Repository: tapdata/tapdata (language: python)

Issue:
TAP-2699 Date processor node occurs an error
Closes TAP-2699


Discussion / hints:

Fix patch:
diff --git a/iengine/api/src/main/java/io/tapdata/common/sharecdc/EventType.java b/iengine/api/src/main/java/io/tapdata/common/sharecdc/EventType.java
new file mode 100644
index 0000000000..0acfadb213
--- /dev/null
+++ b/iengine/api/src/main/java/io/tapdata/common/sharecdc/EventType.java
@@ -0,0 +1,10 @@
+package io.tapdata.common.sharecdc;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-05-16 22:10
+ **/
+public enum EventType {
+	BEFORE, AFTER;
+}
diff --git a/iengine/api/src/main/java/io/tapdata/common/sharecdc/IllegalDatePredicate.java b/iengine/api/src/main/java/io/tapdata/common/sharecdc/IllegalDatePredicate.java
new file mode 100644
index 0000000000..fd78da0e8b
--- /dev/null
+++ b/iengine/api/src/main/java/io/tapdata/common/sharecdc/IllegalDatePredicate.java
@@ -0,0 +1,10 @@
+package io.tapdata.common.sharecdc;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-05-16 21:31
+ **/
+public interface IllegalDatePredicate {
+	boolean test(Object value);
+}
diff --git a/iengine/api/src/main/java/io/tapdata/common/sharecdc/ShareCdcUtil.java b/iengine/api/src/main/java/io/tapdata/common/sharecdc/ShareCdcUtil.java
index 0499d26422..e4bdd005a9 100644
--- a/iengine/api/src/main/java/io/tapdata/common/sharecdc/ShareCdcUtil.java
+++ b/iengine/api/src/main/java/io/tapdata/common/sharecdc/ShareCdcUtil.java
@@ -9,8 +9,10 @@
 import com.tapdata.tm.commons.dag.logCollector.LogCollectorNode;
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import io.tapdata.common.SettingService;
+import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.dml.TapRecordEvent;
 import io.tapdata.entity.utils.DataMap;
+import io.tapdata.flow.engine.V2.util.TapEventUtil;
 import io.tapdata.pdk.apis.functions.connector.source.ConnectionConfigWithTables;
 import org.apache.commons.collections4.MapUtils;
 import org.apache.logging.log4j.LogManager;
@@ -178,31 +180,52 @@ public static List<Connections> getConnectionIds(Node<?> node, Function<Collecti
 	public static void iterateAndHandleSpecialType(Map<String, Object> map, Function<Object, Object> handleFunc) {
 		if(null == map) return;
 		if(null == handleFunc) return;
-		iterateAndHandleMap(map, handleFunc);
+		iterateAndHandleMap(map, handleFunc, null, null, null);
 	}
 
-	private static void iterateAndHandleMap(Map<String, Object> map, Function<Object, Object> handleFunc) {
-		if(null == map) return;
+	public static void iterateAndHandleSpecialType(Map<String, Object> map, Function<Object, Object> handleFunc,
+												   IllegalDatePredicate illegalDatePredicate, TapEvent tapEvent, EventType eventType) {
+		if (null == map) return;
+		if (null == handleFunc) return;
+		iterateAndHandleMap(map, handleFunc, illegalDatePredicate, tapEvent, eventType);
+	}
+
+	private static void iterateAndHandleMap(Map<String, Object> map, Function<Object, Object> handleFunc, IllegalDatePredicate illegalDatePredicate, TapEvent tapEvent, EventType eventType) {
+		if (null == map) return;
 		for (Map.Entry<String, Object> entry : map.entrySet()) {
 			Object value = entry.getValue();
 
 			if (value instanceof Map) {
-				iterateAndHandleMap((Map<String, Object>) value, handleFunc);
+				iterateAndHandleMap((Map<String, Object>) value, handleFunc, illegalDatePredicate, tapEvent, eventType);
 			} else if (value instanceof Collection) {
-				iterateAndHandleCollection((Collection<?>) value, handleFunc);
-			}else{
-				map.put(entry.getKey(), handleFunc.apply(value));
+				iterateAndHandleCollection((Collection<?>) value, handleFunc, illegalDatePredicate, tapEvent, eventType);
+			} else {
+				Object result = handleFunc.apply(value);
+				if (null != illegalDatePredicate && null != tapEvent && null != eventType && illegalDatePredicate.test(result)) {
+					TapEventUtil.setContainsIllegalDate(tapEvent, true);
+					switch (eventType) {
+						case BEFORE:
+							TapEventUtil.addBeforeIllegalDateField(tapEvent, entry.getKey());
+							break;
+						case AFTER:
+							TapEventUtil.addAfterIllegalDateField(tapEvent, entry.getKey());
+							break;
+						default:
+							break;
+					}
+				}
+				map.put(entry.getKey(), result);
 			}
 		}
 	}
 
-	private static void iterateAndHandleCollection(Collection<?> _collection, Function<Object, Object> handleFunc) {
-		if(null == _collection) return;
+	private static void iterateAndHandleCollection(Collection<?> _collection, Function<Object, Object> handleFunc, IllegalDatePredicate illegalDatePredicate, TapEvent tapEvent, EventType eventType) {
+		if (null == _collection) return;
 		for (Object obj : _collection) {
 			if (obj instanceof Map) {
-				iterateAndHandleMap((Map<String, Object>) obj, handleFunc);
+				iterateAndHandleMap((Map<String, Object>) obj, handleFunc, illegalDatePredicate, tapEvent, eventType);
 			} else if (obj instanceof Collection) {
-				iterateAndHandleCollection((Collection<?>) obj, handleFunc);
+				iterateAndHandleCollection((Collection<?>) obj, handleFunc, illegalDatePredicate, tapEvent, eventType);
 			} else {
 				// This situation will not be processed temporarily
 				// 1. Replacing elements in collection has performance issues
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java
index db202af3f4..a08c575ff0 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java
@@ -310,7 +310,7 @@ public ProcessorBaseContext getProcessorBaseContext() {
 	}
 
 	protected TapCodecsFilterManager initFilterCodec() {
-		return TapCodecUtil.genericCodecsFilterManager();
+		return TapCodecUtil.createEngineCodecsFilterManger();
 	}
 
 	protected TapValueTransform transformFromTapValue(TapdataEvent tapdataEvent) {
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastSchemaTargetNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastSchemaTargetNode.java
index 3b3e791437..2d7f50613f 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastSchemaTargetNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastSchemaTargetNode.java
@@ -21,13 +21,10 @@
 import io.tapdata.entity.schema.TapIndex;
 import io.tapdata.entity.schema.TapIndexField;
 import io.tapdata.entity.schema.TapTable;
-import io.tapdata.entity.schema.type.TapType;
-import io.tapdata.entity.schema.value.TapValue;
-import io.tapdata.entity.simplify.TapSimplify;
-import io.tapdata.entity.utils.JavaTypesToTapTypes;
 import io.tapdata.entity.utils.ReflectionUtil;
 import io.tapdata.error.VirtualTargetExCode_14;
 import io.tapdata.exception.TapCodeException;
+import io.tapdata.flow.engine.util.ProcessNodeSchemaUtil;
 import io.tapdata.flow.engine.V2.script.ObsScriptLogger;
 import io.tapdata.flow.engine.V2.util.TapEventUtil;
 import io.tapdata.pdk.core.utils.CommonUtils;
@@ -248,32 +245,9 @@ private TapTable getNewTapTable(TapRecordEvent tapEvent) {
 			LinkedHashMap<String, TapField> oldNameFieldMap = getOldNameFieldMap(tapEvent.getTableId());
 			for (Map.Entry<String, Object> entry : after.entrySet()) {
 				String fieldName = entry.getKey();
-				if (obsLogger.isDebugEnabled()) {
-					obsLogger.debug("entry type: {} - {}", fieldName, entry.getValue().getClass());
-				}
-				TapType tapType;
-				if (entry.getValue() instanceof TapValue) {
-					TapValue<?, ?> tapValue = (TapValue<?, ?>) entry.getValue();
-					tapType = tapValue.getTapType();
-				} else {
-					tapType = JavaTypesToTapTypes.toTapType(entry.getValue());
-					if (tapType == null) {
-						tapType = TapSimplify.tapRaw();
-					}
-				}
-				TapField tapField = null;
-				if (oldNameFieldMap != null) {
-					TapField oldTapField = oldNameFieldMap.get(fieldName);
-					if (oldTapField != null && oldTapField.getTapType() != null
-							&& (oldTapField.getTapType().getType() == tapType.getType() || tapType.getType() == TapType.TYPE_RAW)) {
-						tapField = oldTapField;
-					}
-				}
-				if (tapField == null) {
-					tapField = new TapField().name(fieldName).tapType(tapType);
-				}
-				tapTable.add(tapField);
+				ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, fieldName, entry.getValue(), obsLogger);
 			}
+			ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, after);
 		}
 
 		LinkedHashMap<String, TapField> nameFieldMap = tapTable.getNameFieldMap();
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
index db44479425..2b7d4b3aa8 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
@@ -678,7 +678,7 @@ protected void doNormalCDC() {
 		});
 		StreamReadMultiConnectionFunction streamReadMultiConnectionFunction = Optional.ofNullable(connectionConfigWithTables).map(configWithTables -> {
 			// first config add heartbeat table to list
-			Optional.of(cdcDelayCalculation.addHeartbeatTable(configWithTables.get(0).getTables())).map(joinHeartbeat -> executeAspect(SourceJoinHeartbeatAspect.class, () -> new SourceJoinHeartbeatAspect().dataProcessorContext(dataProcessorContext).joinHeartbeat(joinHeartbeat)));
+			Optional.of(cdcDelayCalculation.addHeartbeatTable(configWithTables.get(0).getTables())).ifPresent(joinHeartbeat -> executeAspect(SourceJoinHeartbeatAspect.class, () -> new SourceJoinHeartbeatAspect().dataProcessorContext(dataProcessorContext).joinHeartbeat(joinHeartbeat)));
 			return connectorNode.getConnectorFunctions().getStreamReadMultiConnectionFunction();
 		}).orElse(null);
 
@@ -716,7 +716,7 @@ protected void doNormalCDC() {
 				}
 				tables.addAll(tapTableMap.keySet());
 				excludeRemoveTable(tables);
-				Optional.of(cdcDelayCalculation.addHeartbeatTable(tables)).map(joinHeartbeat -> executeAspect(SourceJoinHeartbeatAspect.class, () -> new SourceJoinHeartbeatAspect().dataProcessorContext(dataProcessorContext).joinHeartbeat(joinHeartbeat)));
+				Optional.of(cdcDelayCalculation.addHeartbeatTable(tables)).ifPresent(joinHeartbeat -> executeAspect(SourceJoinHeartbeatAspect.class, () -> new SourceJoinHeartbeatAspect().dataProcessorContext(dataProcessorContext).joinHeartbeat(joinHeartbeat)));
 				anyError = () -> {
 					if (null != streamReadFuncAspect) {
 						executeAspect(streamReadFuncAspect.state(StreamReadFuncAspect.STATE_CALLBACK_RAW_DATA).streamReadConsumer(streamReadConsumer));
@@ -734,7 +734,7 @@ protected void doNormalCDC() {
 				streamReadFunctionName = streamReadFunction.getClass().getSimpleName();
 				tables.addAll(tapTableMap.keySet());
 				excludeRemoveTable(tables);
-				Optional.of(cdcDelayCalculation.addHeartbeatTable(tables)).map(joinHeartbeat -> executeAspect(SourceJoinHeartbeatAspect.class, () -> new SourceJoinHeartbeatAspect().dataProcessorContext(dataProcessorContext).joinHeartbeat(joinHeartbeat)));
+				Optional.of(cdcDelayCalculation.addHeartbeatTable(tables)).ifPresent(joinHeartbeat -> executeAspect(SourceJoinHeartbeatAspect.class, () -> new SourceJoinHeartbeatAspect().dataProcessorContext(dataProcessorContext).joinHeartbeat(joinHeartbeat)));
 				anyError = () -> {
 					streamReadFunction.streamRead(getConnectorNode().getConnectorContext(), tables,
 							syncProgress.getStreamOffsetObj(), increaseReadSize, streamReadConsumer);
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java
index 55c8991584..5c14f010b5 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java
@@ -148,14 +148,10 @@ public abstract class HazelcastTargetPdkBaseNode extends HazelcastPdkBaseNode {
     protected ISyncMetricCollector syncMetricCollector;
 
 	protected Boolean unwindProcess = false;
+	protected boolean illegalDateAcceptable = false;
 
 	public HazelcastTargetPdkBaseNode(DataProcessorContext dataProcessorContext) {
 		super(dataProcessorContext);
-//        queueConsumerThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), r -> {
-//            Thread thread = new Thread(r);
-//            thread.setName(String.format("Target-Queue-Consumer-%s[%s]", getNode().getName(), getNode().getId()));
-//            return thread;
-//        });
 		queueConsumerThreadPool = AsyncUtils.createThreadPoolExecutor(String.format("Target-Queue-Consumer-%s[%s]@task-%s", getNode().getName(), getNode().getId(), dataProcessorContext.getTaskDto().getName()), 1, new ConnectorOnTaskThreadGroup(dataProcessorContext), TAG);
 		//threadPoolExecutorEx = AsyncUtils.createThreadPoolExecutor("Target-" + getNode().getName() + "@task-" + dataProcessorContext.getTaskDto().getName(), 1, new ConnectorOnTaskThreadGroup(dataProcessorContext), TAG);
 		flushOffsetExecutor = new ScheduledThreadPoolExecutor(1, r->{
@@ -177,6 +173,7 @@ protected void doInit(@NotNull Context context) throws TapCodeException {
 			initTargetQueueConsumer();
 			initTargetConcurrentProcessorIfNeed();
 			initTapEventFilter();
+			initIllegalDateAcceptable();
 			flushOffsetExecutor.scheduleWithFixedDelay(this::saveToSnapshot, 10L, 10L, TimeUnit.SECONDS);
 		});
 		Thread.currentThread().setName(String.format("Target-Process-%s[%s]", getNode().getName(), getNode().getId()));
@@ -192,7 +189,7 @@ protected void doInitWithDisableNode(@NotNull Context context) throws TapCodeExc
 		Thread.currentThread().setName(String.format("Target-Process-%s[%s]", getNode().getName(), getNode().getId()));
 	}
 
-	private void initExactlyOnceWriteIfNeed() {
+	protected void initExactlyOnceWriteIfNeed() {
 		checkExactlyOnceWriteEnableResult = enableExactlyOnceWrite();
 		if (!checkExactlyOnceWriteEnableResult.getEnable()) {
 			if (StringUtils.isNotBlank(checkExactlyOnceWriteEnableResult.getMessage())) {
@@ -314,12 +311,12 @@ protected boolean createTable(TapTable tapTable, AtomicBoolean succeed) {
 		return createdTable;
 	}
 
-	private void initTargetQueueConsumer() {
+	protected void initTargetQueueConsumer() {
 		this.queueConsumerThreadPool.submit(this::queueConsume);
 		obsLogger.debug("Initialize target event handler complete");
 	}
 
-	private void initTargetConcurrentProcessorIfNeed() {
+	protected void initTargetConcurrentProcessorIfNeed() {
 		if (getNode() instanceof DataParentNode) {
 			DataParentNode<?> dataParentNode = (DataParentNode<?>) getNode();
 			final Boolean initialConcurrentInConfig = dataParentNode.getInitialConcurrent();
@@ -377,7 +374,7 @@ protected boolean isCDCConcurrent(Boolean cdcConcurrent) {
 		return cdcConcurrent;
 	}
 
-	private void initTargetVariable() {
+	protected void initTargetVariable() {
 		this.uploadDagService = new AtomicBoolean(false);
 		this.insertMetadata = new CopyOnWriteArrayList<>();
 		this.updateMetadata = new ConcurrentHashMap<>();
@@ -399,7 +396,7 @@ private void initTargetVariable() {
 		obsLogger.debug("Initialize target write queue complete, capacity: {}", writeQueueCapacity);
 	}
 
-	private void createPdkAndInit(@NotNull Context context) {
+	protected void createPdkAndInit(@NotNull Context context) {
 		if (getNode() instanceof TableNode || getNode() instanceof DatabaseNode) {
 			try {
 				createPdkConnectorNode(dataProcessorContext, context.hazelcastInstance());
@@ -410,7 +407,7 @@ private void createPdkAndInit(@NotNull Context context) {
 		}
 	}
 
-	private void initTapEventFilter() {
+	protected void initTapEventFilter() {
 		this.targetTapEventFilter = TargetTapEventFilter.create();
 		//this.targetTapEventFilter.addFilter(new DeleteConditionFieldFilter());
 	}
@@ -830,11 +827,12 @@ private void flushShareCdcTableMetrics(TapdataEvent tapdataEvent) {
 		}
 	}
 
-	private void handleTapdataShareLogEvent(List<TapdataShareLogEvent> tapdataShareLogEvents, TapdataEvent tapdataEvent, Consumer<TapdataEvent> consumer) {
+	protected void handleTapdataShareLogEvent(List<TapdataShareLogEvent> tapdataShareLogEvents, TapdataEvent tapdataEvent, Consumer<TapdataEvent> consumer) {
 		TapEvent tapEvent = tapdataEvent.getTapEvent();
 		if (tapEvent instanceof TapRecordEvent) {
 			TapRecordEvent tapRecordEvent = (TapRecordEvent) tapEvent;
 			String targetTableName = getTgtTableNameFromTapEvent(tapEvent);
+			replaceIllegalDateWithNullIfNeed(tapRecordEvent);
 			fromTapValue(TapEventUtil.getBefore(tapRecordEvent), codecsFilterManager, targetTableName);
 			fromTapValue(TapEventUtil.getAfter(tapRecordEvent), codecsFilterManager, targetTableName);
 		}
@@ -863,7 +861,7 @@ private void handleTapdataHeartbeatEvent(TapdataEvent tapdataEvent) {
 		flushOffsetByTapdataEventForNoConcurrent(new AtomicReference<>(tapdataEvent));
 	}
 
-	private TapRecordEvent handleTapdataRecordEvent(TapdataEvent tapdataEvent) {
+	protected TapRecordEvent handleTapdataRecordEvent(TapdataEvent tapdataEvent) {
 		TapRecordEvent tapRecordEvent = (TapRecordEvent) tapdataEvent.getTapEvent();
 		if (writeStrategy.equals(MergeTableProperties.MergeType.appendWrite.name())) {
 			// When append write, update and delete event turn into insert event
@@ -882,6 +880,7 @@ private TapRecordEvent handleTapdataRecordEvent(TapdataEvent tapdataEvent) {
 			}
 		}
 		String targetTableName = getTgtTableNameFromTapEvent(tapRecordEvent);
+		replaceIllegalDateWithNullIfNeed(tapRecordEvent);
 		fromTapValue(TapEventUtil.getBefore(tapRecordEvent), codecsFilterManager, targetTableName);
 		fromTapValue(TapEventUtil.getAfter(tapRecordEvent), codecsFilterManager, targetTableName);
 		fromTapValueMergeInfo(tapdataEvent);
@@ -912,6 +911,38 @@ protected void recursiveMergeInfoTransformFromTapValue(List<MergeLookupResult> m
 		}
 	}
 
+	protected void initIllegalDateAcceptable() {
+		if (null == dataProcessorContext.getConnections()) {
+			return;
+		}
+		List<Capability> capabilities = dataProcessorContext.getConnections().getCapabilities();
+		if(CollectionUtils.isNotEmpty(capabilities) && capabilities.stream().anyMatch(cap -> null != cap && ConnectionOptions.DML_ILLEGAL_DATE_ACCEPTABLE.equals(cap.getId()))) {
+			illegalDateAcceptable = true;
+		}
+	}
+
+	protected void replaceIllegalDateWithNullIfNeed(TapRecordEvent event){
+		boolean containsIllegalDate = event.getContainsIllegalDate();
+		if (containsIllegalDate && !illegalDateAcceptable){
+			Map<String, Object> before = Optional.ofNullable(TapEventUtil.getBefore(event)).orElse(new HashMap<>());
+			Map<String, List<String>> illegalFieldMap = Optional.ofNullable(TapEventUtil.getIllegalField(event)).orElse(new HashMap<>());
+			List<String> beforeIllegal = Optional.ofNullable(illegalFieldMap.get("before")).orElse(new ArrayList<>());
+			List<String> afterIllegal = Optional.ofNullable(illegalFieldMap.get("after")).orElse(new ArrayList<>());
+			for (String filedName : beforeIllegal) {
+				Object value = before.get(filedName);
+				if (null != value){
+					before.put(filedName, null);
+				}
+			}
+			Map<String, Object> after = TapEventUtil.getAfter(event);
+			for (String filedName : afterIllegal) {
+				Object value = after.get(filedName);
+				if (null != value){
+					after.put(filedName, null);
+				}
+			}
+		}
+	}
 	private boolean handleExactlyOnceWriteCacheIfNeed(TapdataEvent tapdataEvent, List<TapRecordEvent> exactlyOnceWriteCache) {
 		if (!tableEnableExactlyOnceWrite(tapdataEvent.getSyncStage(), getTgtTableNameFromTapEvent(tapdataEvent.getTapEvent()))) {
 			return false;
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkShareCDCNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkShareCDCNode.java
index ab238173e6..b79af0a5a3 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkShareCDCNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkShareCDCNode.java
@@ -32,9 +32,10 @@ protected void doInit(@NotNull Context context) throws TapCodeException {
 		this.targetBatch = 10000;
 		this.targetBatchIntervalMs = 1000;
 		this.processorNode.doInit(context,tapProcessorNodeContext);
+		this.illegalDateAcceptable = true;
 	}
 
-	private TapProcessorNodeContext initTapProcessorNodeContext() {
+	protected TapProcessorNodeContext initTapProcessorNodeContext() {
 		return TapProcessorNodeContext.builder()
 				.codecsFilterManager(codecsFilterManager)
 				.running(running)
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessor.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessor.java
index acdb7e4b43..b3d8ed5a3c 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessor.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessor.java
@@ -237,13 +237,13 @@ public void process(List<TapdataEvent> tapdataEvents, boolean async) {
 
 		try {
 			AtomicBoolean singleMode = new AtomicBoolean(false);
-			TapdataEvent offsetEvent = null;
+            TapdataEvent offsetEvent = null;
 			for (TapdataEvent tapdataEvent : tapdataEvents) {
 				if (!isRunning()) {
 					break;
 				}
 				if (tapdataEvent.isDML()) {
-					offsetEvent = processDML(tapdataEvent, singleMode);
+					processDML(tapdataEvent, singleMode);
 				} else if (tapdataEvent.isDDL()) {
 					processDDL(tapdataEvent);
 				} else {
@@ -253,6 +253,9 @@ public void process(List<TapdataEvent> tapdataEvents, boolean async) {
 						processSignalWithWait(tapdataEvent);
 					}
 				}
+                if (null != tapdataEvent.getBatchOffset() || null != tapdataEvent.getStreamOffset()) {
+                    offsetEvent = tapdataEvent;
+                }
 			}
 			if (null != offsetEvent) {
 				generateWatermarkEvent(offsetEvent);
@@ -266,7 +269,7 @@ public void process(List<TapdataEvent> tapdataEvents, boolean async) {
 		}
 	}
 
-	protected TapdataEvent processDML(TapdataEvent tapdataEvent, AtomicBoolean singleMode) throws InterruptedException {
+	protected void processDML(TapdataEvent tapdataEvent, AtomicBoolean singleMode) throws InterruptedException {
 		String tableName = "";
 		try {
 			final TapEvent tapEvent = tapdataEvent.getTapEvent();
@@ -286,16 +289,12 @@ protected TapdataEvent processDML(TapdataEvent tapdataEvent, AtomicBoolean singl
 			final LinkedBlockingQueue<PartitionEvent<TapdataEvent>> queue = partitionsQueue.get(partition);
 			final NormalEvent<TapdataEvent> normalEvent = new NormalEvent<>(eventSeq.incrementAndGet(), tapdataEvent);
 			offer2QueueIfRunning(queue, normalEvent, wrapPartitionErrorMsg(partition, PROCESS_QUEUE_IF_FULL_WAITING_FOR_ENQUEUE_MESSAGE));
-			if (null != tapdataEvent.getBatchOffset() || null != tapdataEvent.getStreamOffset()) {
-				return tapdataEvent;
-			}
 		} catch (InterruptedException e) {
 			throw e;
 		} catch (Exception e) {
 			String msg = String.format(" tableName: %s, %s", tableName, e.getMessage());
 			throw new RuntimeException(msg, e);
 		}
-		return null;
 	}
 
 	protected void processDDL(TapdataEvent tapdataEvent) throws InterruptedException {
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNode.java
index 418864cec4..ed6c7a80fb 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNode.java
@@ -64,12 +64,6 @@ public HazelcastDateProcessorNode(ProcessorBaseContext processorBaseContext) {
 		initConfig();
 	}
 
-	@Override
-	protected TapCodecsFilterManager initFilterCodec() {
-		TapCodecsRegistry tapCodecsRegistry = TapCodecsRegistry.create();
-		return TapCodecUtil.getCodecsFilterManager(tapCodecsRegistry);
-	}
-
 	private void initConfig() {
 		Node node = getNode();
 		if (node instanceof DateProcessorNode) {
@@ -162,9 +156,12 @@ private void addTime(List<String> addTimeFields, final Map<String, Object> after
 		}
 	}
 
-	private void addTime(List<String> addTimeFields, Map<String, Object> after, String tableName, String k, String oldK, Object v) {
+	protected void addTime(List<String> addTimeFields, Map<String, Object> after, String tableName, String k, String oldK, Object v) {
 		if (addTimeFields.contains(k)) {
 			if (v instanceof DateTime) {
+				if (((DateTime) v).isContainsIllegal()) {
+					return;
+				}
 				v = ((DateTime) v).toInstant();
 				if (add) {
 					v = ((Instant) v).plus(hours, ChronoUnit.HOURS);
@@ -173,7 +170,8 @@ private void addTime(List<String> addTimeFields, Map<String, Object> after, Stri
 				}
 				after.replace(oldK, new DateTime((Instant) v));
 			} else if (v != null) {
-				throw new TapCodeException(TaskDateProcessorExCode_17.SELECTED_TYPE_IS_NON_TIME + "table: " + tableName + "type :" + k + "value: " + v);
+				throw new TapCodeException(TaskDateProcessorExCode_17.SELECTED_TYPE_IS_NON_TIME, "table: " + tableName
+						+ ", key: " + k + ", type: " + v.getClass().getName() + ", value: " + v);
 			}
 		}
 	}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNode.java
index 74aaa02122..f96bbf861c 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNode.java
@@ -172,7 +172,8 @@ private List<TapdataEvent> batchProcess(List<BatchEventWrapper> batchEventWrappe
 	@Override
 	protected TapCodecsFilterManager initFilterCodec() {
 		TapCodecsRegistry tapCodecsRegistry = TapCodecsRegistry.create();
-		return TapCodecUtil.getCodecsFilterManager(tapCodecsRegistry);
+		TapCodecUtil.registerToTapValue(tapCodecsRegistry);
+		return TapCodecsFilterManager.create(tapCodecsRegistry);
 	}
 
 	@Override
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/util/ProcessNodeSchemaUtil.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/util/ProcessNodeSchemaUtil.java
new file mode 100644
index 0000000000..630d403d82
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/util/ProcessNodeSchemaUtil.java
@@ -0,0 +1,120 @@
+package io.tapdata.flow.engine.util;
+
+import cn.hutool.core.collection.CollUtil;
+import io.tapdata.entity.schema.TapField;
+import io.tapdata.entity.schema.TapTable;
+import io.tapdata.entity.schema.type.TapType;
+import io.tapdata.entity.schema.value.TapArrayValue;
+import io.tapdata.entity.schema.value.TapMapValue;
+import io.tapdata.entity.schema.value.TapValue;
+import io.tapdata.entity.simplify.TapSimplify;
+import io.tapdata.entity.utils.JavaTypesToTapTypes;
+import io.tapdata.observable.logging.ObsLogger;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+
+public class ProcessNodeSchemaUtil {
+    private ProcessNodeSchemaUtil() {
+    }
+
+    public static void scanTapField(TapTable tapTable, Map<String, TapField> oldNameFieldMap, String fieldName, Object value, ObsLogger obsLogger) {
+        if (obsLogger.isDebugEnabled()) {
+            obsLogger.debug("entry type: {} - {}", fieldName, null != value ? value.getClass() : "null");
+        }
+        TapType tapType;
+        if (value instanceof TapValue) {
+            TapValue<?, ?> tapValue = (TapValue<?, ?>) value;
+            tapType = tapValue.getTapType();
+        } else {
+            tapType = JavaTypesToTapTypes.toTapType(value);
+        }
+        if (tapType == null) {
+            tapType = TapSimplify.tapRaw();
+        }
+        scanSubFieldFromEventValue(tapTable, oldNameFieldMap, fieldName, value, tapType, obsLogger);
+        TapField tapField = null;
+        if (oldNameFieldMap != null) {
+            TapField oldTapField = oldNameFieldMap.get(fieldName);
+            if (oldTapField != null && oldTapField.getTapType() != null
+                    && (oldTapField.getTapType().getType() == tapType.getType() || tapType.getType() == TapType.TYPE_RAW)) {
+                tapField = oldTapField;
+            }
+        }
+        if (tapField == null) {
+            tapField = new TapField().name(fieldName).tapType(tapType);
+        }
+        tapTable.add(tapField);
+    }
+
+    protected static void scanSubFieldFromEventValue(TapTable tapTable, Map<String, TapField> oldNameFieldMap, String entryKey, Object entryValue, TapType tapType, ObsLogger obsLogger) {
+        if (null == entryValue) {
+            return;
+        }
+        switch (tapType.getType()) {
+            case TapType.TYPE_MAP:
+            case TapType.TYPE_ARRAY:
+                scanObject(tapTable, oldNameFieldMap, entryKey, entryValue, obsLogger);
+                break;
+            default:
+                //do nothing
+        }
+    }
+
+    protected static void scanObject(TapTable tapTable, Map<String, TapField> oldNameFieldMap, String fatherFieldName, Object entryValue, ObsLogger obsLogger) {
+        if (entryValue instanceof Map) {
+            scanMap(tapTable, oldNameFieldMap, fatherFieldName, (Map<String, Object>) entryValue, obsLogger);
+        } else if (entryValue instanceof TapMapValue) {
+            scanMap(tapTable, oldNameFieldMap, fatherFieldName, ((TapMapValue) entryValue).getValue(), obsLogger);
+        } else if (entryValue instanceof Collection) {
+            scanArray(tapTable, oldNameFieldMap, fatherFieldName, (Collection<Object>) entryValue, obsLogger);
+        } else if (entryValue instanceof TapArrayValue) {
+            scanArray(tapTable, oldNameFieldMap, fatherFieldName, ((TapArrayValue) entryValue).getValue(), obsLogger);
+        }
+    }
+
+    protected static void scanMap(TapTable tapTable, Map<String, TapField> oldNameFieldMap, String fatherFieldName, Map<String, Object> mapValue, ObsLogger obsLogger) {
+        mapValue.forEach((key, value) -> {
+            String currentFieldName = String.format("%s.%s", fatherFieldName, key);
+            scanTapField(tapTable, oldNameFieldMap, currentFieldName, value, obsLogger);
+        });
+    }
+
+    protected static void scanArray(TapTable tapTable, Map<String, TapField> oldNameFieldMap, String fatherFieldName, Collection<Object> arrayValue, ObsLogger obsLogger) {
+        arrayValue.stream().filter(Objects::nonNull).forEach(arrayItem -> scanObject(tapTable, oldNameFieldMap, fatherFieldName, arrayItem, obsLogger));
+    }
+
+    public static void retainedOldSubFields(TapTable tapTable, Map<String, TapField> oldNameFieldMap, Map<String, Object> afterValue) {
+        if (CollUtil.isEmpty(oldNameFieldMap)) {
+            return;
+        }
+        oldNameFieldMap.entrySet().stream().filter(entry -> {
+            String key = entry.getKey();
+            int index = key.indexOf(".");
+            if (index > 0) {
+                String fatherFieldName = key.substring(0, index);
+                return needRetainedOldSubField(tapTable, afterValue, oldNameFieldMap, key, fatherFieldName);
+            }
+            return false;
+        }).forEach(e -> tapTable.add(e.getValue()));
+    }
+
+    private static boolean needRetainedOldSubField(TapTable tapTable,
+                                                   Map<String, Object> afterValue,
+                                                   Map<String, TapField> oldNameFieldMap,
+                                                   String key, String fatherFieldName) {
+        if (afterValue.containsKey(fatherFieldName) && !afterValue.containsKey(key)) {
+            TapField oldFatherField = oldNameFieldMap.get(fatherFieldName);
+            if (null == oldFatherField) {
+                return false;
+            }
+            TapField afterField = tapTable.getNameFieldMap().get(fatherFieldName);
+            if (null == afterField || null == afterField.getTapType()) {
+                return false;
+            }
+            return afterField.getTapType().getType() == oldFatherField.getTapType().getType();
+        }
+        return false;
+    }
+}
diff --git a/iengine/iengine-app/src/main/resources/pluginKit.properties b/iengine/iengine-app/src/main/resources/pluginKit.properties
index 3807c5ac56..7479e56ed0 100644
--- a/iengine/iengine-app/src/main/resources/pluginKit.properties
+++ b/iengine/iengine-app/src/main/resources/pluginKit.properties
@@ -12,7 +12,7 @@ maven.compiler.target=1.8
 project.build.sourceEncoding=UTF-8
 project.reporting.outputEncoding=UTF-8
 proxy.file.name=plugin-kit-v1.0-SNAPSHOT
-tapdata.api.verison=1.3.5-SNAPSHOT
-tapdata.pdk.api.verison=1.3.5-SNAPSHOT
+tapdata.api.verison=1.3.6-SNAPSHOT
+tapdata.pdk.api.verison=1.3.6-SNAPSHOT
 tapdata.pdk.connector.core.version=1.0-SNAPSHOT
-tapdata.pdk.runner.verison=1.6-SNAPSHOT
+tapdata.pdk.runner.verison=1.7-SNAPSHOT
diff --git a/iengine/iengine-common/pom.xml b/iengine/iengine-common/pom.xml
index 25b46ae861..090a1ee17f 100644
--- a/iengine/iengine-common/pom.xml
+++ b/iengine/iengine-common/pom.xml
@@ -329,7 +329,7 @@
         <dependency>
             <groupId>io.tapdata</groupId>
             <artifactId>tapdata-pdk-runner</artifactId>
-            <version>1.6-SNAPSHOT</version>
+            <version>1.7-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>io.tapdata</groupId>
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/cache/PdkDataSourceRowsGetter.java b/iengine/iengine-common/src/main/java/com/tapdata/cache/PdkDataSourceRowsGetter.java
index 03b94b3109..f3fdc69c69 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/cache/PdkDataSourceRowsGetter.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/cache/PdkDataSourceRowsGetter.java
@@ -80,7 +80,7 @@ public PdkDataSourceRowsGetter(DataFlowCacheConfig dataFlowCacheConfig,
 		}
 
 		this.codecsFilterManager = connectorNode.getCodecsFilterManager();
-		this.genericCodecFilterManager = TapCodecUtil.genericCodecsFilterManager();
+		this.genericCodecFilterManager = TapCodecUtil.createEngineCodecsFilterManger();
 		;
 	}
 
diff --git a/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/util/TapCodecUtil.java b/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/util/TapCodecUtil.java
index cacd749b3e..a983d2a519 100644
--- a/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/util/TapCodecUtil.java
+++ b/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/util/TapCodecUtil.java
@@ -3,14 +3,11 @@
 import com.tapdata.constant.DateUtil;
 import io.tapdata.entity.codec.TapCodecsRegistry;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
-import io.tapdata.entity.schema.value.DateTime;
-import io.tapdata.entity.schema.value.TapBinaryValue;
-import io.tapdata.entity.schema.value.TapDateTimeValue;
-import io.tapdata.entity.schema.value.TapDateValue;
-import io.tapdata.entity.schema.value.TapTimeValue;
-import io.tapdata.entity.schema.value.TapYearValue;
-import org.jetbrains.annotations.NotNull;
+import io.tapdata.entity.schema.value.*;
+import io.tapdata.entity.utils.ObjectSerializable;
+import io.tapdata.pdk.core.api.impl.serialize.ObjectSerializableImplV2;
 
+import java.nio.charset.StandardCharsets;
 import java.text.ParseException;
 import java.util.Arrays;
 import java.util.TimeZone;
@@ -22,93 +19,187 @@
  **/
 public class TapCodecUtil {
 
-	private static final String TAP_YEAR_BYTE_TAG = "$tapYear$";
-	private static final String TAP_TIME_BYTE_TAG = "$tapTime$";
-	private static final String TAP_DATE_BYTE_TAG = "$tapDate$";
+	protected static final byte TAP_YEAR_BYTE_TAG_V2 = 1;
+	protected static final byte TAP_TIME_BYTE_TAG_V2 = 2;
+	protected static final byte TAP_DATE_BYTE_TAG_V2 = 3;
+	protected static final byte TAP_DATETIME_BYTE_TAG_V2 = 4;
+	protected static final String TAP_YEAR_BYTE_TAG = "$tapYear$";
+	protected static final String TAP_TIME_BYTE_TAG = "$tapTime$";
+	protected static final String TAP_DATE_BYTE_TAG = "$tapDate$";
+	public static final ObjectSerializable OBJECT_SERIALIZABLE = new ObjectSerializableImplV2();
+	public static final String TAG = TapCodecUtil.class.getSimpleName();
+	protected static final byte[] TAG_BYTES = TAG.getBytes(StandardCharsets.UTF_8);
+
+	public static TapCodecsFilterManager createEngineCodecsFilterManger() {
+		TapCodecsFilterManager tapCodecsFilterManager = TapCodecsFilterManager.create(TapCodecsRegistry.create());
+		registerFromTapValue(tapCodecsFilterManager.getCodecsRegistry());
+		registerToTapValue(tapCodecsFilterManager.getCodecsRegistry());
+		return tapCodecsFilterManager;
+	}
+
+	public static void registerFromTapValue(TapCodecsRegistry tapCodecsRegistry) {
+		tapCodecsRegistry.registerFromTapValue(TapDateTimeValue.class, tapValue -> {
+			if (tapValue.getValue().isContainsIllegal()) {
+				tapValue.getValue().setTimeZone(TimeZone.getDefault());
+				return addTag(OBJECT_SERIALIZABLE.fromObject(tapValue.getValue()), TAP_DATETIME_BYTE_TAG_V2);
+			} else {
+				return tapValue.getValue().toInstant();
+			}
+		});
+		tapCodecsRegistry.registerFromTapValue(TapYearValue.class, tapValue -> addTag(OBJECT_SERIALIZABLE.fromObject(tapValue.getValue()), TAP_YEAR_BYTE_TAG_V2));
+		tapCodecsRegistry.registerFromTapValue(TapDateValue.class, tapValue -> addTag(OBJECT_SERIALIZABLE.fromObject(tapValue.getValue()), TAP_DATE_BYTE_TAG_V2));
+		tapCodecsRegistry.registerFromTapValue(TapTimeValue.class, tapValue -> addTag(OBJECT_SERIALIZABLE.fromObject(tapValue.getValue()), TAP_TIME_BYTE_TAG_V2));
+	}
+
+	public static void registerToTapValue(TapCodecsRegistry tapCodecsRegistry) {
+		tapCodecsRegistry.registerToTapValue(byte[].class, (o, tapType) -> {
+			byte[] bytes = (byte[]) o;
+			Object result = toTapValueV2(bytes);
+			if (result instanceof TapValue) {
+				return (TapValue<?, ?>) result;
+			}
+			result = toTapValueV1(bytes);
+			if (result instanceof TapValue) {
+				return (TapValue<?, ?>) result;
+			}
+
+			return new TapBinaryValue((byte[]) o);
+		});
+	}
 
-	public static TapCodecsFilterManager genericCodecsFilterManager() {
-		TapCodecsRegistry tapCodecsRegistry = TapCodecsRegistry.create();
-		tapCodecsRegistry.registerFromTapValue(TapDateTimeValue.class, tapValue -> tapValue.getValue().toInstant());
-		tapCodecsRegistry.registerFromTapValue(TapYearValue.class, tapValue -> {
-			tapValue.getValue().setTimeZone(TimeZone.getDefault());
-			final String yyyy = tapValue.getValue().toFormatString("yyyy");
+	/**
+	 * Do not use this method
+	 * Retaining this method is for unit testing purposes
+	 *
+	 * @param tapValue
+	 * @return
+	 */
+	protected static Object fromTapValueV1(TapValue<?, ?> tapValue) {
+		if (tapValue instanceof TapDateTimeValue) {
+			return ((TapDateTimeValue) tapValue).getValue().toInstant();
+		} else if (tapValue instanceof TapYearValue) {
+			final String yyyy = ((TapYearValue) tapValue).getValue().toFormatString("yyyy");
 			StringBuilder sb = new StringBuilder(TAP_YEAR_BYTE_TAG);
 			sb.append(yyyy);
 			return sb.toString().getBytes();
-		});
-		tapCodecsRegistry.registerFromTapValue(TapDateValue.class, tapValue -> {
-			tapValue.getValue().setTimeZone(TimeZone.getDefault());
-			final String dateString = tapValue.getValue().toFormatString("yyyy-MM-dd");
+		} else if (tapValue instanceof TapDateValue) {
+			((TapDateValue) tapValue).getValue().setTimeZone(TimeZone.getDefault());
+			final String dateString = ((TapDateValue) tapValue).getValue().toFormatString("yyyy-MM-dd");
 			StringBuilder sb = new StringBuilder(TAP_DATE_BYTE_TAG);
 			sb.append(dateString);
 			return sb.toString().getBytes();
-		});
-		tapCodecsRegistry.registerFromTapValue(TapTimeValue.class, tapValue -> {
-			final String timeString = tapValue.getValue().toTimeStr();
+		} else if (tapValue instanceof TapTimeValue) {
+			final String timeString = ((TapTimeValue) tapValue).getValue().toTimeStr();
 			StringBuilder sb = new StringBuilder(TAP_TIME_BYTE_TAG);
 			sb.append(timeString);
 			return sb.toString().getBytes();
-		});
-
-
-		return getCodecsFilterManager(tapCodecsRegistry);
+		}
+		return tapValue;
 	}
 
-	@NotNull
-	public static TapCodecsFilterManager getCodecsFilterManager(TapCodecsRegistry tapCodecsRegistry) {
-		tapCodecsRegistry.registerToTapValue(byte[].class, (o, tapType) -> {
-			byte[] bytes = (byte[]) o;
-
-			// TapYear judgment
-			if (bytes.length > TAP_YEAR_BYTE_TAG.length()) {
-				byte[] yearTagBytes = new byte[TAP_YEAR_BYTE_TAG.length()];
-				System.arraycopy(bytes, 0, yearTagBytes, 0, TAP_YEAR_BYTE_TAG.length());
-				if (Arrays.equals(yearTagBytes, TAP_YEAR_BYTE_TAG.getBytes())) {
-
-					byte[] yearStringBytes = new byte[bytes.length - TAP_YEAR_BYTE_TAG.length()];
-					System.arraycopy(bytes, TAP_YEAR_BYTE_TAG.length(), yearStringBytes, 0, yearStringBytes.length);
-					String yearString = new String(yearStringBytes);
-					try {
-						return new TapYearValue(new DateTime(DateUtil.parse(yearString, "yyyy", null)));
-					} catch (ParseException e) {
-						throw new RuntimeException(e);
-					}
+	private static Object toTapValueV1(byte[] bytes) {
+		if (null == bytes) {
+			return bytes;
+		}
+		// TapYear judgment
+		if (bytes.length > TAP_YEAR_BYTE_TAG.length()) {
+			byte[] yearTagBytes = new byte[TAP_YEAR_BYTE_TAG.length()];
+			System.arraycopy(bytes, 0, yearTagBytes, 0, TAP_YEAR_BYTE_TAG.length());
+			if (Arrays.equals(yearTagBytes, TAP_YEAR_BYTE_TAG.getBytes())) {
+
+				byte[] yearStringBytes = new byte[bytes.length - TAP_YEAR_BYTE_TAG.length()];
+				System.arraycopy(bytes, TAP_YEAR_BYTE_TAG.length(), yearStringBytes, 0, yearStringBytes.length);
+				String yearString = new String(yearStringBytes);
+				try {
+					return new TapYearValue(new DateTime(DateUtil.parse(yearString, "yyyy", null)));
+				} catch (ParseException e) {
+					throw new RuntimeException(e);
 				}
 			}
-
-			// TapDate judgment
-			if (bytes.length > TAP_DATE_BYTE_TAG.length()) {
-				byte[] dateTagBytes = new byte[TAP_DATE_BYTE_TAG.length()];
-				System.arraycopy(bytes, 0, dateTagBytes, 0, TAP_DATE_BYTE_TAG.length());
-				if (Arrays.equals(dateTagBytes, TAP_DATE_BYTE_TAG.getBytes())) {
-
-					byte[] dateStringBytes = new byte[bytes.length - TAP_DATE_BYTE_TAG.length()];
-					System.arraycopy(bytes, TAP_DATE_BYTE_TAG.length(), dateStringBytes, 0, dateStringBytes.length);
-					String dateString = new String(dateStringBytes);
-					try {
-						return new TapDateValue(new DateTime(DateUtil.parse(dateString, "yyyy-MM-dd", null)));
-					} catch (ParseException e) {
-						throw new RuntimeException(e);
-					}
+		}
+
+		// TapDate judgment
+		if (bytes.length > TAP_DATE_BYTE_TAG.length()) {
+			byte[] dateTagBytes = new byte[TAP_DATE_BYTE_TAG.length()];
+			System.arraycopy(bytes, 0, dateTagBytes, 0, TAP_DATE_BYTE_TAG.length());
+			if (Arrays.equals(dateTagBytes, TAP_DATE_BYTE_TAG.getBytes())) {
+
+				byte[] dateStringBytes = new byte[bytes.length - TAP_DATE_BYTE_TAG.length()];
+				System.arraycopy(bytes, TAP_DATE_BYTE_TAG.length(), dateStringBytes, 0, dateStringBytes.length);
+				String dateString = new String(dateStringBytes);
+				try {
+					return new TapDateValue(new DateTime(DateUtil.parse(dateString, "yyyy-MM-dd", null)));
+				} catch (ParseException e) {
+					throw new RuntimeException(e);
 				}
 			}
-
-			// TapTime judgment
-			if (bytes.length > TAP_TIME_BYTE_TAG.length()) {
-				byte[] timeTagBytes = new byte[TAP_TIME_BYTE_TAG.length()];
-				System.arraycopy(bytes, 0, timeTagBytes, 0, TAP_TIME_BYTE_TAG.length());
-				if (Arrays.equals(timeTagBytes, TAP_TIME_BYTE_TAG.getBytes())) {
-
-					byte[] timeStringBytes = new byte[bytes.length - TAP_TIME_BYTE_TAG.length()];
-					System.arraycopy(bytes, TAP_TIME_BYTE_TAG.length(), timeStringBytes, 0, timeStringBytes.length);
-					String timeString = new String(timeStringBytes);
-					return new TapTimeValue(DateTime.withTimeStr(timeString));
-				}
+		}
+
+		// TapTime judgment
+		if (bytes.length > TAP_TIME_BYTE_TAG.length()) {
+			byte[] timeTagBytes = new byte[TAP_TIME_BYTE_TAG.length()];
+			System.arraycopy(bytes, 0, timeTagBytes, 0, TAP_TIME_BYTE_TAG.length());
+			if (Arrays.equals(timeTagBytes, TAP_TIME_BYTE_TAG.getBytes())) {
+
+				byte[] timeStringBytes = new byte[bytes.length - TAP_TIME_BYTE_TAG.length()];
+				System.arraycopy(bytes, TAP_TIME_BYTE_TAG.length(), timeStringBytes, 0, timeStringBytes.length);
+				String timeString = new String(timeStringBytes);
+				return new TapTimeValue(DateTime.withTimeStr(timeString));
 			}
-			return new TapBinaryValue((byte[]) o);
-		});
+		}
+		return bytes;
+	}
+
+	private static Object toTapValueV2(byte[] bytes) {
+		int tagLength = TAG_BYTES.length;
+		if (null == bytes || bytes.length <= tagLength) {
+			return bytes;
+		}
+		byte[] tagBytes = new byte[tagLength];
+		System.arraycopy(bytes, 0, tagBytes, 0, tagLength);
+		if (!Arrays.equals(TAG_BYTES, tagBytes)) {
+			return bytes;
+		}
+		byte valueTag = bytes[tagLength];
+		byte[] valueBytes = new byte[bytes.length - tagLength - 1];
+		System.arraycopy(bytes, tagLength + 1, valueBytes, 0, valueBytes.length);
+		Object valueObj = OBJECT_SERIALIZABLE.toObject(valueBytes);
+		if (!(valueObj instanceof DateTime)) {
+			return bytes;
+		}
+		DateTime dateTime = (DateTime) valueObj;
+		TapValue<?, ?> tapValue;
+		switch (valueTag) {
+			case TAP_DATE_BYTE_TAG_V2:
+				tapValue = new TapDateValue(dateTime);
+				break;
+			case TAP_DATETIME_BYTE_TAG_V2:
+				tapValue = new TapDateTimeValue(dateTime);
+				break;
+			case TAP_YEAR_BYTE_TAG_V2:
+				tapValue = new TapYearValue(dateTime);
+				break;
+			case TAP_TIME_BYTE_TAG_V2:
+				tapValue = new TapTimeValue(dateTime);
+				break;
+			default:
+				return bytes;
+		}
+		return tapValue;
+	}
 
-		return TapCodecsFilterManager.create(tapCodecsRegistry);
+	protected static byte[] addTag(byte[] value, byte valueTag) {
+		if (null == value || value.length == 0) {
+			return value;
+		}
+		byte[] tagBytes = new byte[TAG_BYTES.length + 1];
+		System.arraycopy(TAG_BYTES, 0, tagBytes, 0, TAG_BYTES.length);
+		tagBytes[TAG_BYTES.length] = valueTag;
+		int tagLength = tagBytes.length;
+		byte[] result = new byte[tagLength + value.length];
+		System.arraycopy(tagBytes, 0, result, 0, tagLength);
+		System.arraycopy(value, 0, result, tagLength, value.length);
+		return result;
 	}
 
 }
diff --git a/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/util/TapEventUtil.java b/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/util/TapEventUtil.java
index 024f552289..10ebe10241 100644
--- a/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/util/TapEventUtil.java
+++ b/iengine/iengine-common/src/main/java/io/tapdata/flow/engine/V2/util/TapEventUtil.java
@@ -9,6 +9,8 @@
 import io.tapdata.entity.event.dml.TapRecordEvent;
 import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -43,6 +45,21 @@ public static Map<String, Object> getAfter(TapEvent tapEvent) {
 		}
 		return null;
 	}
+	public static Map<String, List<String>> getIllegalField(TapEvent tapEvent) {
+		Map<String, List<String>> map = new HashMap<>();
+		if (tapEvent instanceof TapInsertRecordEvent) {
+			map.put("after",((TapInsertRecordEvent) tapEvent).getAfterIllegalDateFieldName());
+			return map;
+		} else if (tapEvent instanceof TapUpdateRecordEvent) {
+			map.put("before",((TapUpdateRecordEvent) tapEvent).getBeforeIllegalDateFieldName());
+			map.put("after",((TapUpdateRecordEvent) tapEvent).getAfterIllegalDateFieldName());
+			return map;
+		} else if (tapEvent instanceof TapDeleteRecordEvent) {
+			map.put("before",((TapDeleteRecordEvent) tapEvent).getBeforeIllegalDateFieldName());
+			return map;
+		}
+		return null;
+	}
 
 	public static void setAfter(TapEvent tapEvent, Map<String, Object> after) {
 		if (tapEvent instanceof TapInsertRecordEvent) {
@@ -105,4 +122,38 @@ public static Boolean getIsReplaceEvent(TapEvent tapEvent) {
 		}
 		return false;
 	}
+
+	public static void setContainsIllegalDate(TapEvent tapEvent, boolean containsIllegalDate) {
+		if (tapEvent instanceof TapRecordEvent){
+			((TapRecordEvent) tapEvent).setContainsIllegalDate(containsIllegalDate);
+		}
+	}
+
+	public static void addBeforeIllegalDateField(TapEvent tapEvent, String fieldName) {
+		if (tapEvent instanceof TapUpdateRecordEvent) {
+			if (((TapUpdateRecordEvent) tapEvent).getBeforeIllegalDateFieldName() == null) {
+				((TapUpdateRecordEvent) tapEvent).setBeforeIllegalDateFieldName(new ArrayList<>());
+			}
+			((TapUpdateRecordEvent) tapEvent).getBeforeIllegalDateFieldName().add(fieldName);
+		} else if (tapEvent instanceof TapDeleteRecordEvent) {
+			if (((TapDeleteRecordEvent) tapEvent).getBeforeIllegalDateFieldName() == null) {
+				((TapDeleteRecordEvent) tapEvent).setBeforeIllegalDateFieldName(new ArrayList<>());
+			}
+			((TapDeleteRecordEvent) tapEvent).getBeforeIllegalDateFieldName().add(fieldName);
+		}
+	}
+
+	public static void addAfterIllegalDateField(TapEvent tapEvent, String fieldName) {
+		if (tapEvent instanceof TapInsertRecordEvent) {
+			if (((TapInsertRecordEvent) tapEvent).getAfterIllegalDateFieldName() == null) {
+				((TapInsertRecordEvent) tapEvent).setAfterIllegalDateFieldName(new ArrayList<>());
+			}
+			((TapInsertRecordEvent) tapEvent).getAfterIllegalDateFieldName().add(fieldName);
+		} else if (tapEvent instanceof TapUpdateRecordEvent) {
+			if (((TapUpdateRecordEvent) tapEvent).getAfterIllegalDateFieldName() == null) {
+				((TapUpdateRecordEvent) tapEvent).setAfterIllegalDateFieldName(new ArrayList<>());
+			}
+			((TapUpdateRecordEvent) tapEvent).getAfterIllegalDateFieldName().add(fieldName);
+		}
+	}
 }
diff --git a/iengine/pom.xml b/iengine/pom.xml
index 801c69b12d..7ace683364 100644
--- a/iengine/pom.xml
+++ b/iengine/pom.xml
@@ -810,7 +810,7 @@
             <dependency>
                 <groupId>com.tapdata</groupId>
                 <artifactId>tapdata-api</artifactId>
-                <version>1.3.5-SNAPSHOT</version>
+                <version>1.3.6-SNAPSHOT</version>
             </dependency>
             <dependency>
                 <groupId>com.tapdata</groupId>
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/metadatainstance/entity/MetadataInstancesEntity.java b/manager/tm-api/src/main/java/com/tapdata/tm/metadatainstance/entity/MetadataInstancesEntity.java
index be1af9e4f1..8611423c4e 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/metadatainstance/entity/MetadataInstancesEntity.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/metadatainstance/entity/MetadataInstancesEntity.java
@@ -104,6 +104,9 @@ public class MetadataInstancesEntity extends BaseEntity {
     private Long tmCurrentTime;
     private String transformUuid;
 
+    private String beforeDynamicTableName;
+    private String afterDynamicTableName;
+
     /**
      * 是否是虚拟表 'virtual' 'source'
      */
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/HookOneHistoryDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/HookOneHistoryDto.java
new file mode 100644
index 0000000000..dfee0851cf
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/HookOneHistoryDto.java
@@ -0,0 +1,34 @@
+package com.tapdata.tm.webhook.dto;
+
+import com.tapdata.tm.commons.base.dto.BaseDto;
+import lombok.Data;
+
+@Data
+public class HookOneHistoryDto extends BaseDto {
+    String hookId;
+    String url;
+    String requestBody;
+    String requestParams;
+    String requestHeaders;
+    Long requestAt;
+
+    String responseHeaders;
+    String responseResult;
+    String responseStatus;
+    Integer responseCode;
+    Long responseAt;
+
+    String eventType;
+    String type;
+
+    /**
+     * @see com.tapdata.tm.webhook.enums.PingResult
+     */
+    String status;
+
+    /**
+     * @see com.tapdata.tm.webhook.enums.WebHookHistoryStatus
+     */
+    String historyStatus;
+    String historyMessage;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookAlterInfoDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookAlterInfoDto.java
new file mode 100644
index 0000000000..fdc1531926
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookAlterInfoDto.java
@@ -0,0 +1,125 @@
+package com.tapdata.tm.webhook.dto;
+
+import com.tapdata.tm.alarm.constant.AlarmComponentEnum;
+import com.tapdata.tm.alarm.constant.AlarmStatusEnum;
+import com.tapdata.tm.alarm.constant.AlarmTypeEnum;
+import com.tapdata.tm.commons.task.constant.AlarmKeyEnum;
+import com.tapdata.tm.message.constant.Level;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.Map;
+
+@Data
+public class WebHookAlterInfoDto {
+    private String id;
+    private AlarmStatusEnum status;
+    private Level level;
+    private AlarmComponentEnum component;
+    private AlarmTypeEnum type;
+    private String agentId;
+    private String taskId;
+    private String name;
+    private String nodeId;
+    private String node;
+    private AlarmKeyEnum metric;
+    private Integer currentValue;
+    private Integer threshold;
+    private Date firstOccurrenceTime;
+    private Date lastOccurrenceTime;
+    private Date lastNotifyTime;
+    private Integer tally;
+    private String summary;
+    private Date recoveryTime;
+    private Date closeTime;
+    private String closeBy;
+    private String inspectId;
+
+    private String title;
+    private String content;
+    private String smsEvent;
+
+    /**
+     * @see AlarmStatusEnum
+     * */
+    private String statusTxt;
+    /**
+     * @see AlarmComponentEnum
+     * */
+    private String componentTxt;
+    /**
+     * @see AlarmTypeEnum
+     * */
+    private String typeTxt;
+    /**
+     * @see AlarmKeyEnum
+     * */
+    private String metricTxt;
+
+    private transient Map<String, Object> param;
+
+    public void copyTxt() {
+        withStatusTxt(this.status);
+        withComponentTxt(this.component);
+        withMetricTxt(this.metric);
+        withTypeTxt(this.type);
+    }
+
+    public void withStatusTxt(AlarmStatusEnum status) {
+        switch (status) {
+            case ING: this.statusTxt = "正在进行";break;
+            case RECOVER: this.statusTxt = "已恢复";break;
+            case CLOESE: this.statusTxt = "已关闭";break;
+            default: this.statusTxt = "-";
+        }
+    }
+    public void withComponentTxt(AlarmComponentEnum component) {
+        if (AlarmComponentEnum.FE.equals(component)) {
+            this.componentTxt = "引擎";
+            return;
+        }
+        this.componentTxt = "-";
+    }
+    public void withTypeTxt(AlarmTypeEnum type) {
+        this.typeTxt = type.getDesc();
+    }
+    public void withMetricTxt(AlarmKeyEnum key) {
+        switch (key) {
+            case DATANODE_AVERAGE_HANDLE_CONSUME:
+                this.metricTxt = "数据源节点的平均处理耗时超过阀值";break;
+            case TASK_INCREMENT_DELAY:
+                this.metricTxt = "任务的增量延迟超过阀值";break;
+            case SYSTEM_FLOW_EGINGE_DOWN:
+                this.metricTxt = "引擎离线";break;
+            case PROCESSNODE_AVERAGE_HANDLE_CONSUME:
+                this.metricTxt = "节点的平均处理耗时超过阀值";break;
+            case TASK_STATUS_STOP:
+                this.metricTxt = "任务运行停止";break;
+            case SYSTEM_FLOW_EGINGE_UP:
+                this.metricTxt = "引擎上线";break;
+            case TASK_STATUS_ERROR:
+                this.metricTxt = "任务运行错误";break;
+            case INSPECT_COUNT_ERROR:
+                this.metricTxt = "Count校验结果的差异行数大于阈值";break;
+            case TASK_INCREMENT_START:
+                this.metricTxt = "任务增量开始";break;
+            case TASK_FULL_COMPLETE:
+                this.metricTxt = "任务全量完成";break;
+            case INSPECT_VALUE_ERROR:
+                this.metricTxt = "值校验结果的表数据差大于阈值";break;
+            case TASK_INSPECT_ERROR:
+                this.metricTxt = "任务校验出错";break;
+            case DATANODE_CANNOT_CONNECT:
+                this.metricTxt = "数据源无法连接网络";break;
+            case DATANODE_TCP_CONNECT_CONSUME:
+                this.metricTxt = "数据源TCP连接完成";break;
+            case DATANODE_HTTP_CONNECT_CONSUME:
+                this.metricTxt = "数据源连接网络完成";break;
+            case INSPECT_TASK_ERROR:
+                this.metricTxt = "校验任务遇到错误";break;
+            default:
+                this.metricTxt = "-";
+        }
+    }
+
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookHistoryDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookHistoryDto.java
new file mode 100644
index 0000000000..a8412ee2c6
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookHistoryDto.java
@@ -0,0 +1,16 @@
+package com.tapdata.tm.webhook.dto;
+
+import com.tapdata.tm.commons.base.dto.BaseDto;
+import com.tapdata.tm.webhook.entity.HookOneHistory;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class WebHookHistoryDto extends BaseDto {
+    private String hookId;
+    private String sendBy;
+    private List<HookOneHistory> hookEvent;
+    private Boolean delete;
+    private Long eventCount;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookInfoCreateDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookInfoCreateDto.java
new file mode 100644
index 0000000000..e9c4819d8e
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookInfoCreateDto.java
@@ -0,0 +1,59 @@
+package com.tapdata.tm.webhook.dto;
+
+import com.tapdata.tm.commons.base.dto.BaseDto;
+import com.tapdata.tm.webhook.enums.PingResult;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Locale;
+
+@Data
+public class WebHookInfoCreateDto extends BaseDto {
+    private String userId;
+
+    /**
+     * WebHook name
+     */
+    private String hookName;
+
+    /**
+     * WebHook URL
+     */
+    private String url;
+
+    /**
+     * 是否启用
+     */
+    private Boolean open;
+
+
+    private String token;
+
+    private String httpUser;
+
+    private String httpPwd;
+
+    private String customHttpHeaders;
+
+    /**
+     *
+     */
+    private String customTemplate;
+
+    /**
+     * ping状态
+     */
+    private PingResult pingResult;
+
+    /**
+     * @see com.tapdata.tm.webhook.enums.HookType hookName
+     */
+    private List<String> hookTypes;
+
+    /**
+     * 备注
+     */
+    private String mark;
+
+    private Locale locale;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookInfoDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookInfoDto.java
new file mode 100644
index 0000000000..de26e8d965
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/dto/WebHookInfoDto.java
@@ -0,0 +1,58 @@
+package com.tapdata.tm.webhook.dto;
+
+import com.tapdata.tm.commons.base.dto.BaseDto;
+import com.tapdata.tm.webhook.enums.PingResult;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Locale;
+
+@Data
+public class WebHookInfoDto extends BaseDto {
+    private String userId;
+
+    /**
+     * WebHook name
+     */
+    private String hookName;
+
+    /**
+     * WebHook URL
+     */
+    private String url;
+
+    /**
+     * 是否启用
+     */
+    private Boolean open;
+
+    private String token;
+
+    private String httpUser;
+
+    private String httpPwd;
+
+    private String customHttpHeaders;
+
+    /**
+     *
+     */
+    private String customTemplate;
+
+    /**
+     * ping状态
+     */
+    private PingResult pingResult;
+
+    /**
+     * @see com.tapdata.tm.webhook.enums.HookType hookName
+     */
+    private List<String> hookTypes;
+
+    /**
+     * 备注
+     */
+    private String mark;
+
+    private Locale locale;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/HookOneHistory.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/HookOneHistory.java
new file mode 100644
index 0000000000..e642e0dc27
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/HookOneHistory.java
@@ -0,0 +1,39 @@
+package com.tapdata.tm.webhook.entity;
+
+import com.tapdata.tm.base.entity.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class HookOneHistory extends BaseEntity {
+    private String hookId;
+    private String url;
+    private String requestHeaders;
+    private String requestBody;
+    private String requestParams;
+    private Long requestAt;
+
+    private String responseHeaders;
+    private String responseResult;
+    private String responseStatus;
+    private Integer responseCode;
+    private Long responseAt;
+
+
+    private String eventType;
+    private String type;
+
+    /**
+     * @see com.tapdata.tm.webhook.enums.PingResult
+     */
+    private String status;
+
+    /**
+     * @see com.tapdata.tm.webhook.enums.WebHookHistoryStatus
+     */
+    private String historyStatus;
+    private String historyMessage;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/WebHookEvent.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/WebHookEvent.java
new file mode 100644
index 0000000000..42582d922c
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/WebHookEvent.java
@@ -0,0 +1,77 @@
+package com.tapdata.tm.webhook.entity;
+
+import java.util.List;
+
+public class WebHookEvent {
+    String type;
+    String metric;
+    Object event;
+    List<String> userId;
+    private String title;
+    private String content;
+
+    public static WebHookEvent of() {
+        return new WebHookEvent();
+    }
+
+    public WebHookEvent withType(String type) {
+        this.type = type;
+        return this;
+    }
+    public WebHookEvent withMetric(String metric) {
+        this.metric = metric;
+        return this;
+    }
+
+    public WebHookEvent withEvent(Object event) {
+        this.event = event;
+        return this;
+    }
+
+    public WebHookEvent withUserId(List<String> userIds) {
+        this.userId = userIds;
+        return this;
+    }
+
+    public WebHookEvent withTitle(String title) {
+        this.title = title;
+        return this;
+    }
+
+    public WebHookEvent withContent(String content) {
+        this.content = content;
+        return this;
+    }
+
+    public String getMetric() {
+        return metric;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public Object getEvent() {
+        return event;
+    }
+
+    public List<String> getUserId() {
+        return userId;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/WebHookHistory.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/WebHookHistory.java
new file mode 100644
index 0000000000..e04263c6e6
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/WebHookHistory.java
@@ -0,0 +1,26 @@
+package com.tapdata.tm.webhook.entity;
+
+import com.tapdata.tm.base.entity.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.List;
+
+/**
+ * @author Gavin'Xiao
+ * @date 2024/5/10
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Document(collection = "WebHookHistory")
+public class WebHookHistory extends BaseEntity {
+    private String hookId;
+    private List<HookOneHistory> hookEvent;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/WebHookInfo.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/WebHookInfo.java
new file mode 100644
index 0000000000..8f48a3f21b
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/entity/WebHookInfo.java
@@ -0,0 +1,72 @@
+package com.tapdata.tm.webhook.entity;
+
+import com.tapdata.tm.base.entity.BaseEntity;
+import com.tapdata.tm.webhook.enums.PingResult;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.List;
+
+/**
+ * @author Gavin'Xiao
+ * @date 2024/5/10
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Document(collection = "WebHookInfo")
+public class WebHookInfo extends BaseEntity {
+
+    private String userId;
+
+    /**
+     * WebHook name
+     */
+    private String hookName;
+
+    /**
+     * WebHook URL
+     */
+    private String url;
+
+    /**
+     * 是否启用
+     */
+    private Boolean open;
+
+    private String token;
+
+    private String httpUser;
+
+    private String httpPwd;
+
+    private String customHttpHeaders;
+
+    /**
+     *
+     */
+    private String customTemplate;
+
+    /**
+     * ping状态
+     */
+    private PingResult pingResult;
+
+    /**
+     * 备注
+     */
+    private String mark;
+
+    /**
+     * @see com.tapdata.tm.webhook.enums.HookType hookName
+     */
+    private List<String> hookTypes;
+
+    private boolean deleted;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/ConstVariable.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/ConstVariable.java
new file mode 100644
index 0000000000..76b9c84c87
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/ConstVariable.java
@@ -0,0 +1,10 @@
+package com.tapdata.tm.webhook.enums;
+
+public class ConstVariable {
+    public static final String HOOK_ID = "hookId";
+    public static final String HOOK_EVENT = "hookEvent";
+    public static final String DELETED = "deleted";
+
+    public static final String TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION = "TapOssNonSupportFunctionException";
+
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/HookType.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/HookType.java
new file mode 100644
index 0000000000..9aaca1c58f
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/HookType.java
@@ -0,0 +1,22 @@
+package com.tapdata.tm.webhook.enums;
+
+public enum HookType {
+    ALTER("task.alter", "webHookTaskAlter"),
+    PING("webhook.ping", "webKookPingServer"),
+    ;
+    String hookName;
+    String hookBeanName;
+
+    HookType(String hookName, String hookBeanName) {
+        this.hookName = hookName;
+        this.hookBeanName = hookBeanName;
+    }
+
+    public String getHookName() {
+        return hookName;
+    }
+
+    public String getHookBeanName() {
+        return hookBeanName;
+    }
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/PingResult.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/PingResult.java
new file mode 100644
index 0000000000..df30cbbb11
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/PingResult.java
@@ -0,0 +1,5 @@
+package com.tapdata.tm.webhook.enums;
+
+public enum PingResult {
+    SUCCEED, FAILED;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/WebHookHistoryStatus.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/WebHookHistoryStatus.java
new file mode 100644
index 0000000000..ed93e87409
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/WebHookHistoryStatus.java
@@ -0,0 +1,6 @@
+package com.tapdata.tm.webhook.enums;
+
+public enum  WebHookHistoryStatus {
+    /** sending / retry / complete */
+    FAILED, ING, SUCCEED
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/params/HistoryPageParam.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/params/HistoryPageParam.java
new file mode 100644
index 0000000000..f444e3acdd
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/params/HistoryPageParam.java
@@ -0,0 +1,10 @@
+package com.tapdata.tm.webhook.params;
+
+import lombok.Data;
+
+@Data
+public class HistoryPageParam {
+    String hookId;
+    int pageFrom;
+    int pageSize;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookAdapterService.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookAdapterService.java
new file mode 100644
index 0000000000..4e9489b231
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookAdapterService.java
@@ -0,0 +1,17 @@
+package com.tapdata.tm.webhook.server;
+
+import com.tapdata.tm.webhook.dto.WebHookInfoDto;
+import com.tapdata.tm.webhook.entity.HookOneHistory;
+import com.tapdata.tm.webhook.entity.WebHookEvent;
+
+import java.util.List;
+
+public interface WebHookAdapterService {
+    void send(WebHookEvent event);
+
+    void sendAsync(WebHookEvent event, List<WebHookInfoDto> myOpenHookInfoList);
+
+    HookOneHistory sendAndSave(WebHookEvent event, WebHookInfoDto myOpenHookInfo);
+
+    HookOneHistory send(WebHookEvent event, WebHookInfoDto myOpenHookInfo);
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookHistoryService.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookHistoryService.java
new file mode 100644
index 0000000000..9f76da6d34
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookHistoryService.java
@@ -0,0 +1,25 @@
+package com.tapdata.tm.webhook.server;
+
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.webhook.dto.HookOneHistoryDto;
+import com.tapdata.tm.webhook.entity.HookOneHistory;
+import com.tapdata.tm.webhook.params.HistoryPageParam;
+import com.tapdata.tm.webhook.vo.WebHookHistoryInfoVo;
+import org.springframework.data.util.Pair;
+
+import java.util.List;
+import java.util.Locale;
+
+public interface WebHookHistoryService<V> {
+
+    Page<V> list(HistoryPageParam pageParam, UserDetail userDetail, Locale locale);
+
+    long pushHistory(String hookId, List<HookOneHistory> history);
+
+    long pushManyHistory(List<Pair<String, List<HookOneHistory>>> historyInfos);
+
+    void deleteHookHistory(String hookId, UserDetail user);
+
+    WebHookHistoryInfoVo reSend(HookOneHistoryDto history, UserDetail user);
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookHttpUtilService.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookHttpUtilService.java
new file mode 100644
index 0000000000..b66731a8b2
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookHttpUtilService.java
@@ -0,0 +1,16 @@
+package com.tapdata.tm.webhook.server;
+
+import com.tapdata.tm.webhook.entity.HookOneHistory;
+
+import java.util.Map;
+
+public interface WebHookHttpUtilService {
+    boolean checkURL(String url);
+
+    HookOneHistory post(String url,
+                               Map<String, Object> head,
+                               Map<String, Object> urlParam,
+                               Object body);
+
+    HookOneHistory post(HookOneHistory history);
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookService.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookService.java
new file mode 100644
index 0000000000..0eb22e94c3
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/WebHookService.java
@@ -0,0 +1,34 @@
+package com.tapdata.tm.webhook.server;
+
+import com.tapdata.tm.base.dto.Filter;
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.webhook.dto.HookOneHistoryDto;
+import com.tapdata.tm.webhook.dto.WebHookInfoDto;
+
+import java.util.List;
+import java.util.Locale;
+
+public interface WebHookService<V> {
+    Page<V> list(Filter filter, UserDetail userDetail, Locale locale);
+
+    V findWebHookByHookId(String hookId, UserDetail user);
+
+    V create(WebHookInfoDto dto, UserDetail user);
+
+    V update(WebHookInfoDto dto, UserDetail user);
+
+    V updatePingResult(WebHookInfoDto dto);
+
+    List<V> close(String[] ids, UserDetail userDetail);
+
+    void delete(String[] ids, UserDetail userDetail);
+
+    List<WebHookInfoDto> findMyOpenHookInfoList(String hookType, String metric, List<String> userId);
+
+    void checkUrl(String url);
+
+    HookOneHistoryDto ping(WebHookInfoDto webHookEvent, UserDetail userDetail);
+
+    List<V> reOpen(String[] strings, UserDetail loginUser);
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/AdapterService.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/AdapterService.java
new file mode 100644
index 0000000000..c4ec3cca94
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/AdapterService.java
@@ -0,0 +1,34 @@
+package com.tapdata.tm.webhook.server.impl;
+
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.webhook.dto.WebHookInfoDto;
+import com.tapdata.tm.webhook.entity.HookOneHistory;
+import com.tapdata.tm.webhook.entity.WebHookEvent;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import com.tapdata.tm.webhook.server.WebHookAdapterService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service("AdapterService")
+public class AdapterService implements WebHookAdapterService {
+    @Override
+    public void send(WebHookEvent event) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public void sendAsync(WebHookEvent event, List<WebHookInfoDto> myOpenHookInfoList) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public HookOneHistory sendAndSave(WebHookEvent event, WebHookInfoDto myOpenHookInfo) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public HookOneHistory send(WebHookEvent event, WebHookInfoDto myOpenHookInfo) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/HistoryService.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/HistoryService.java
new file mode 100644
index 0000000000..36db8360a0
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/HistoryService.java
@@ -0,0 +1,42 @@
+package com.tapdata.tm.webhook.server.impl;
+
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.webhook.dto.HookOneHistoryDto;
+import com.tapdata.tm.webhook.entity.HookOneHistory;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import com.tapdata.tm.webhook.params.HistoryPageParam;
+import com.tapdata.tm.webhook.server.WebHookHistoryService;
+import com.tapdata.tm.webhook.vo.WebHookHistoryInfoVo;
+import org.springframework.data.util.Pair;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Locale;
+
+@Service("HistoryService")
+public class HistoryService implements WebHookHistoryService<WebHookHistoryInfoVo> {
+    @Override
+    public Page<WebHookHistoryInfoVo> list(HistoryPageParam pageParam, UserDetail userDetail, Locale locale) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public void deleteHookHistory(String hookId, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public WebHookHistoryInfoVo reSend(HookOneHistoryDto history, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);    }
+
+    @Override
+    public long pushManyHistory(List<Pair<String, List<HookOneHistory>>> historyInfos) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);    }
+
+    @Override
+    public long pushHistory(String hookId, List<HookOneHistory> history) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/HttpUtilService.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/HttpUtilService.java
new file mode 100644
index 0000000000..9e9f9635bf
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/HttpUtilService.java
@@ -0,0 +1,27 @@
+package com.tapdata.tm.webhook.server.impl;
+
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.webhook.entity.HookOneHistory;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import com.tapdata.tm.webhook.server.WebHookHttpUtilService;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+@Service("HttpUtilService")
+public class HttpUtilService implements WebHookHttpUtilService {
+    @Override
+    public boolean checkURL(String url) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public HookOneHistory post(String url, Map<String, Object> head, Map<String, Object> urlParam, Object body) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public HookOneHistory post(HookOneHistory history) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/WebHookDefaultService.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/WebHookDefaultService.java
new file mode 100644
index 0000000000..c9d78e4bda
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/server/impl/WebHookDefaultService.java
@@ -0,0 +1,73 @@
+package com.tapdata.tm.webhook.server.impl;
+
+import com.tapdata.tm.base.dto.Filter;
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.webhook.dto.HookOneHistoryDto;
+import com.tapdata.tm.webhook.dto.WebHookInfoDto;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import com.tapdata.tm.webhook.server.WebHookService;
+import com.tapdata.tm.webhook.vo.WebHookInfoVo;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Locale;
+
+@Service("WebHookDefaultService")
+public class WebHookDefaultService implements WebHookService<WebHookInfoVo> {
+    @Override
+    public Page<WebHookInfoVo> list(Filter filter, UserDetail userDetail, Locale locale) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public WebHookInfoVo findWebHookByHookId(String hookId, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public WebHookInfoVo create(WebHookInfoDto dto, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public WebHookInfoVo update(WebHookInfoDto dto, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public WebHookInfoVo updatePingResult(WebHookInfoDto dto) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public List<WebHookInfoVo> close(String[] ids, UserDetail userDetail) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public void delete(String[] ids, UserDetail userDetail) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public List<WebHookInfoDto> findMyOpenHookInfoList(String hookType, String metric, List<String> userId) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public void checkUrl(String url) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public HookOneHistoryDto ping(WebHookInfoDto webHookEvent, UserDetail userDetail) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public List<WebHookInfoVo> reOpen(String[] strings, UserDetail loginUser) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/vo/WebHookHistoryInfoVo.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/vo/WebHookHistoryInfoVo.java
new file mode 100644
index 0000000000..e4d579e110
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/vo/WebHookHistoryInfoVo.java
@@ -0,0 +1,33 @@
+package com.tapdata.tm.webhook.vo;
+
+import lombok.Data;
+
+@Data
+public class WebHookHistoryInfoVo {
+    String id;
+    String hookId;
+    String url;
+    String eventType;
+    String type;
+    String status;
+
+    String requestId;
+    String requestHeaders;
+    String requestBody;
+    String requestParams;
+    Long requestAt;
+
+    String responseHeaders;
+    String responseResult;
+    String responseStatus;
+    Integer responseCode;
+    Long responseAt;
+
+    /**
+     * @see com.tapdata.tm.webhook.enums.WebHookHistoryStatus
+     */
+    String historyStatus;
+    String historyMessage;
+
+    Long createAt;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/vo/WebHookInfoVo.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/vo/WebHookInfoVo.java
new file mode 100644
index 0000000000..e0d00fc9ec
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/vo/WebHookInfoVo.java
@@ -0,0 +1,56 @@
+package com.tapdata.tm.webhook.vo;
+
+import com.tapdata.tm.commons.base.dto.BaseDto;
+import com.tapdata.tm.webhook.enums.PingResult;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class WebHookInfoVo extends BaseDto {
+
+    private String userId;
+
+    /**
+     * WebHook name
+     */
+    private String hookName;
+
+    /**
+     * WebHook URL
+     */
+    private String url;
+
+    /**
+     * 是否启用
+     */
+    private Boolean open;
+
+    private String token;
+
+    private String httpUser;
+
+    private String httpPwd;
+
+    private String customHttpHeaders;
+
+    /**
+     *
+     */
+    private String customTemplate;
+
+    /**
+     * ping状态
+     */
+    private PingResult pingResult;
+
+    /**
+     * @see com.tapdata.tm.webhook.enums.HookType hookName
+     */
+    private List<String> hookTypes;
+
+    /**
+     * 备注
+     */
+    private String mark;
+}
diff --git a/manager/tm-common/pom.xml b/manager/tm-common/pom.xml
index 82761eab88..926646376a 100644
--- a/manager/tm-common/pom.xml
+++ b/manager/tm-common/pom.xml
@@ -143,7 +143,7 @@
     <dependency>
         <groupId>io.tapdata</groupId>
         <artifactId>tapdata-pdk-runner</artifactId>
-        <version>1.4-SNAPSHOT</version>
+        <version>1.7-SNAPSHOT</version>
       <exclusions>
         <exclusion>
           <artifactId>commons-lang3</artifactId>
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/DAGDataServiceImpl.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/DAGDataServiceImpl.java
index d0c7826274..c02d258003 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/DAGDataServiceImpl.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/DAGDataServiceImpl.java
@@ -397,6 +397,9 @@ private List<Schema> createOrUpdateSchemaForDataNode(ObjectId dataSourceId, List
             metadataInstancesDto.setDeleted(false);
             metadataInstancesDto.setSource(sourceDto);
 
+            metadataInstancesDto.setBeforeDynamicTableName(schema.getBeforeDynamicTableName());
+            metadataInstancesDto.setAfterDynamicTableName(schema.getAfterDynamicTableName());
+
             // 需要将 Connections 的id 转换为 metadata instance 中 meta_type=database 的 id
             //   使用 qualified name 作为条件查询
             metadataInstancesDto.setDatabaseId(dataSourceMetadataInstance.getId().toHexString());
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/CouplingLocation.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/CouplingLocation.java
new file mode 100644
index 0000000000..a71f1ba6a2
--- /dev/null
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/CouplingLocation.java
@@ -0,0 +1,5 @@
+package com.tapdata.tm.commons.dag.dynamic;
+
+public enum CouplingLocation {
+    PREFIX,SUFFIX;
+}
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableConfig.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableConfig.java
new file mode 100644
index 0000000000..65f715b57f
--- /dev/null
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableConfig.java
@@ -0,0 +1,75 @@
+package com.tapdata.tm.commons.dag.dynamic;
+
+import java.io.Serializable;
+import java.util.Map;
+
+public class DynamicTableConfig implements Serializable {
+    private static final long serialVersionUID = -6192500357089533037L;
+    /**
+     * generate dynamic table name rule type
+     */
+    private String ruleType;
+
+    private String couplingSymbols;
+
+    private CouplingLocation couplingLocation;
+
+    private transient Map<String, Object> params;
+
+
+    public static DynamicTableConfig of() {
+        return new DynamicTableConfig();
+    }
+
+    public DynamicTableConfig withRuleType(String ruleType) {
+        this.ruleType = ruleType;
+        return this;
+    }
+
+    public DynamicTableConfig withCouplingSymbols(String couplingSymbols) {
+        this.couplingSymbols = couplingSymbols;
+        return this;
+    }
+
+    public DynamicTableConfig withCouplingLocation(CouplingLocation couplingLocation) {
+        this.couplingLocation = couplingLocation;
+        return this;
+    }
+
+    public DynamicTableConfig withParams(Map<String, Object> params) {
+        this.params = params;
+        return this;
+    }
+
+    public void setRuleType(String ruleType) {
+        this.ruleType = ruleType;
+    }
+
+    public void setCouplingSymbols(String couplingSymbols) {
+        this.couplingSymbols = couplingSymbols;
+    }
+
+    public void setCouplingLocation(CouplingLocation couplingLocation) {
+        this.couplingLocation = couplingLocation;
+    }
+
+    public void setParams(Map<String, Object> params) {
+        this.params = params;
+    }
+
+    public String getRuleType() {
+        return ruleType;
+    }
+
+    public String getCouplingSymbols() {
+        return couplingSymbols;
+    }
+
+    public CouplingLocation getCouplingLocation() {
+        return couplingLocation;
+    }
+
+    public Map<String, Object> getParams() {
+        return params;
+    }
+}
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableNameByDate.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableNameByDate.java
new file mode 100644
index 0000000000..d74e5c2298
--- /dev/null
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableNameByDate.java
@@ -0,0 +1,30 @@
+package com.tapdata.tm.commons.dag.dynamic;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * dynamic table name:
+ *  Directly add the suffix based on the current time:
+ *      [${PREFIX}${couplingSymbols}]${oldTableName}[${couplingSymbols}${SUFFIX}]
+ *      default couplingSymbols: _
+ *      default couplingLocation: SUFFIX
+ *      default dynamic char: yyyy_MM_dd
+ * @author Gavin'Xiao
+ * https://github.com/11000100111010101100111
+ * */
+class DynamicTableNameByDate extends DynamicTableStage {
+    public static final String DATE_FORMAT = "yyyy_MM_dd";
+    public DynamicTableNameByDate(String tableName, DynamicTableConfig dynamicRule) {
+        super(tableName, dynamicRule);
+    }
+
+    @Override
+    public DynamicTableResult genericTableName() {
+        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
+        String dateFormatStr = sdf.format(new Date());
+        return DynamicTableResult.of()
+                .withOldTableName(tableName)
+                .withDynamicName(genericWithCouplingLocationAndCouplingSymbols(dateFormatStr));
+    }
+}
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableNameUtil.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableNameUtil.java
new file mode 100644
index 0000000000..9a0631d3c4
--- /dev/null
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableNameUtil.java
@@ -0,0 +1,29 @@
+package com.tapdata.tm.commons.dag.dynamic;
+
+import com.tapdata.tm.error.TapDynamicTableNameExCode_35;
+import io.tapdata.exception.TapCodeException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+public class DynamicTableNameUtil {
+    private DynamicTableNameUtil(){}
+
+    public static DynamicTableResult getDynamicTable(String tableName, DynamicTableConfig rule) {
+        DynamicTableRule dynamicTableRule = null == rule ? DynamicTableRule.DEFAULT : DynamicTableRule.getRule(rule.getRuleType());
+        Class<? extends DynamicTableStage> stage = dynamicTableRule.stage;
+        try {
+            Constructor<? extends DynamicTableStage> constructor = stage.getConstructor(String.class, DynamicTableConfig.class);
+            DynamicTableStage dynamicTableStage = constructor.newInstance(tableName, rule);
+            return dynamicTableStage.genericTableName();
+        } catch (InstantiationException e) {
+            throw new TapCodeException(TapDynamicTableNameExCode_35.DYNAMIC_INSTANTIATION_FAILED, "Fail to get instantiation for " + dynamicTableRule.name + ", message: " + e.getMessage());
+        } catch (InvocationTargetException e) {
+            throw new TapCodeException(TapDynamicTableNameExCode_35.DYNAMIC_INVOCATION_FAILED, "Fail to get invocation for "+ dynamicTableRule.name + ", message: " + e.getMessage());
+        } catch (NoSuchMethodException e) {
+            throw new TapCodeException(TapDynamicTableNameExCode_35.UN_SUPPORT_DYNAMIC_RULE, "No such method: " + stage.getName() + ".genericTableName()");
+        } catch (IllegalAccessException e) {
+            throw new TapCodeException(TapDynamicTableNameExCode_35.DYNAMIC_METHOD_ACCESS_FAILED, "Can not access method: " + stage.getName() + ".genericTableName()");
+        }
+    }
+}
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableResult.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableResult.java
new file mode 100644
index 0000000000..c5717aef77
--- /dev/null
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableResult.java
@@ -0,0 +1,25 @@
+package com.tapdata.tm.commons.dag.dynamic;
+
+public class DynamicTableResult {
+    String oldName;
+    String dynamicName;
+    public static DynamicTableResult of() {
+        return new DynamicTableResult();
+    }
+    public DynamicTableResult withOldTableName(String oldName) {
+        this.oldName = oldName;
+        return this;
+    }
+    public DynamicTableResult withDynamicName(String dynamicName) {
+        this.dynamicName = dynamicName;
+        return this;
+    }
+
+    public String getOldName() {
+        return oldName;
+    }
+
+    public String getDynamicName() {
+        return dynamicName;
+    }
+}
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableRule.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableRule.java
new file mode 100644
index 0000000000..9bb7805677
--- /dev/null
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableRule.java
@@ -0,0 +1,20 @@
+package com.tapdata.tm.commons.dag.dynamic;
+
+public enum DynamicTableRule {
+    DEFAULT("default", DynamicTableNameByDate.class),
+    ;
+    String name;
+    Class<? extends DynamicTableStage> stage;
+    DynamicTableRule(String name, Class<? extends DynamicTableStage> stage) {
+        this.name = name;
+        this.stage = stage;
+    }
+
+    public static DynamicTableRule getRule(String ruleName) {
+        if (null == ruleName) return DEFAULT;
+        for (DynamicTableRule value : values()) {
+            if (value.name.equals(ruleName)) return value;
+        }
+        return DEFAULT;
+    }
+}
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableStage.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableStage.java
new file mode 100644
index 0000000000..93d0de07ad
--- /dev/null
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableStage.java
@@ -0,0 +1,32 @@
+package com.tapdata.tm.commons.dag.dynamic;
+
+import java.util.Optional;
+
+abstract class DynamicTableStage {
+    public static final String FORMAT = "%s%s%s";
+    public static final String DEFAULT_COUPLING_SYMBOLS = "_";
+    protected String tableName;
+    protected DynamicTableConfig dynamicRule;
+
+    protected DynamicTableStage(String tableName, DynamicTableConfig dynamicRule) {
+        this.tableName = tableName;
+        if (null == dynamicRule) {
+            dynamicRule = DynamicTableConfig.of()
+                    .withRuleType(DynamicTableRule.DEFAULT.name)
+                    .withCouplingLocation(CouplingLocation.SUFFIX)
+                    .withCouplingSymbols(DEFAULT_COUPLING_SYMBOLS);
+        }
+        this.dynamicRule = dynamicRule;
+    }
+
+    abstract DynamicTableResult genericTableName();
+
+    protected String genericWithCouplingLocationAndCouplingSymbols(String dynamicChar) {
+        String couplingSymbols = Optional.ofNullable(dynamicRule.getCouplingSymbols()).orElse(DEFAULT_COUPLING_SYMBOLS);
+        if (CouplingLocation.PREFIX.equals(dynamicRule.getCouplingLocation())) {
+            return String.format(FORMAT, dynamicChar, couplingSymbols, tableName);
+        } else {
+            return String.format(FORMAT, tableName, couplingSymbols, dynamicChar);
+        }
+    }
+}
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java
index 9720f3b192..56a1fd2428 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java
@@ -159,7 +159,7 @@ public List<Schema> mergeSchema(List<List<Schema>> inputSchemas, List<Schema> sc
             }
             handleAppendWrite(schema);
         }
-        return outputSchema;
+        return SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, outputSchema);
     }
 
     private List<TableIndex> updateIndexDelField(List<TableIndex> indices, Map<String, String> filedMapping) {
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/TableNode.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/TableNode.java
index a02b3043e2..cf61119d16 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/TableNode.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/TableNode.java
@@ -4,13 +4,15 @@
 import com.tapdata.tm.commons.dag.EqField;
 import com.tapdata.tm.commons.dag.NodeType;
 import com.tapdata.tm.commons.dag.SchemaTransformerResult;
+import com.tapdata.tm.commons.dag.dynamic.DynamicTableConfig;
+import com.tapdata.tm.commons.dag.dynamic.DynamicTableNameUtil;
+import com.tapdata.tm.commons.dag.dynamic.DynamicTableResult;
 import com.tapdata.tm.commons.dag.event.WriteEvent;
 import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
 import com.tapdata.tm.commons.schema.Field;
 import com.tapdata.tm.commons.schema.Schema;
 import com.tapdata.tm.commons.schema.SchemaUtils;
 import com.tapdata.tm.commons.task.dto.JoinTable;
-import com.tapdata.tm.commons.task.dto.MergeTableProperties;
 import io.tapdata.entity.event.ddl.TapDDLEvent;
 import io.tapdata.pdk.apis.entity.QueryOperator;
 import lombok.Data;
@@ -40,6 +42,17 @@ public class TableNode extends DataNode {
     /** */
     @EqField
     private String tableName;
+
+    /** Do you want to enable dynamic table names and add suffixes according to the rules*/
+    @EqField
+    private Boolean needDynamicTableName;
+
+    /** Default rule is DateTime{yyyy-mm-dd}
+     * @see com.tapdata.tm.commons.dag.dynamic.DynamicTableRule
+     * */
+    @EqField
+    private DynamicTableConfig dynamicTableRule;
+
     /** 全量自定义sql*/
     @EqField
     private String totalsql;
@@ -195,6 +208,7 @@ protected Schema loadSchema(List<String> includes) {
 
     @Override
     public Schema mergeSchema(List<Schema> inputSchemas, Schema schema, DAG.Options options) {
+        DynamicTableResult dynamicTableResult = this.dynamicTableName(schema);
         if (StringUtils.isBlank(tableName)) {
             return null;
         }
@@ -230,8 +244,9 @@ public Schema mergeSchema(List<Schema> inputSchemas, Schema schema, DAG.Options
                 result.setSourceFieldCount((int) (result.getSourceFieldCount() - count1));
         }
         outputSchema.setOriginalName(tableName);
+        updateSchemaAfterDynamicTableName(outputSchema, dynamicTableResult.getOldName(), dynamicTableResult.getDynamicName());
         handleAppendWrite(outputSchema);
-        return outputSchema;
+        return SchemaUtils.removeSubFieldsWhichFromFreeSchema(service.getDataSource(connectionId), outputSchema);
     }
 
     @Override
@@ -298,4 +313,50 @@ public static class CdcPollingField implements Serializable {
         /** 指定的轮询字段默认值 */
         private String defaultValue;
     }
+
+    protected DynamicTableResult dynamicTableName(Schema schema) {
+        if (!Boolean.TRUE.equals(needDynamicTableName)) {
+            this.tableName = getSchemaName(schema);
+            updateSchemaAfterDynamicTableName(schema, tableName, null == schema ? null : schema.getAfterDynamicTableName());
+            return DynamicTableResult.of();
+        }
+        if (null == dynamicTableRule) {
+            dynamicTableRule = DynamicTableConfig.of();
+        }
+        String baseTable = Optional.ofNullable(getSchemaName(schema)).orElse(tableName);
+        if (null != tableName && null != schema && tableName.equals(schema.getAfterDynamicTableName())) {
+            baseTable = Optional.ofNullable(schema.getBeforeDynamicTableName()).orElse(tableName);
+        }
+        DynamicTableResult dynamicTable = DynamicTableNameUtil.getDynamicTable(
+                baseTable,
+                dynamicTableRule
+        );
+        if (null != dynamicTable) {
+            this.tableName = dynamicTable.getDynamicName();
+            updateSchemaAfterDynamicTableName(schema, dynamicTable.getOldName(), this.tableName);
+        }
+        return dynamicTable;
+    }
+
+    protected String getSchemaName(Schema schema) {
+        if (null == schema) return tableName;
+        if (null != dynamicTableRule) {
+            String afterDynamicTableName = schema.getAfterDynamicTableName();
+            if (null != afterDynamicTableName && !afterDynamicTableName.equals(tableName)) {
+                return tableName;
+            }
+        }
+        String beforeDynamicTableName = schema.getBeforeDynamicTableName();
+        if (null == beforeDynamicTableName) {
+            return tableName;
+        }
+        return beforeDynamicTableName;
+    }
+
+    protected void updateSchemaAfterDynamicTableName(Schema schema, String before, String after) {
+        if (null != schema) {
+            schema.setAfterDynamicTableName(after);
+            schema.setBeforeDynamicTableName(before);
+        }
+    }
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/MetadataInstancesDto.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/MetadataInstancesDto.java
index 2ef6d9a633..51db6ab52a 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/MetadataInstancesDto.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/MetadataInstancesDto.java
@@ -110,6 +110,8 @@ public class MetadataInstancesDto extends BaseDto {
 
      private String description;
 
+    private String beforeDynamicTableName;
+    private String afterDynamicTableName;
 
     /**
      * 是否是虚拟表 'virtual' 'source'
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/Schema.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/Schema.java
index deab53060a..31946e51ed 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/Schema.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/Schema.java
@@ -113,6 +113,9 @@ public Schema copy() throws CloneNotSupportedException {
     private Map<String, PossibleDataTypes> findPossibleDataTypes;
     private boolean hasUpdateField;
 
+    private String beforeDynamicTableName;
+    private String afterDynamicTableName;
+
     /**
      * 是否是虚拟表 'virtual' 'source'
      */
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/SchemaUtils.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/SchemaUtils.java
index 694adfc2cc..f1b73ecf6e 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/SchemaUtils.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/SchemaUtils.java
@@ -1,15 +1,17 @@
 package com.tapdata.tm.commons.schema;
 
+import cn.hutool.core.collection.CollUtil;
 import com.tapdata.tm.commons.dag.process.FieldProcessorNode;
 import com.tapdata.tm.commons.util.MetaDataBuilderUtils;
+import io.tapdata.entity.schema.type.TapArray;
+import io.tapdata.entity.schema.type.TapMap;
+import io.tapdata.entity.simplify.TapSimplify;
 import io.tapdata.entity.utils.InstanceFactory;
 import io.tapdata.entity.utils.JsonParser;
 import io.tapdata.entity.utils.TypeHolder;
 import lombok.Getter;
-import lombok.Setter;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.bson.types.ObjectId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -22,6 +24,9 @@
  * create at 2021/11/10 下午10:37
  */
 public class SchemaUtils {
+    public static final String SCHEMA_FREE = "schema-free";
+    public static final String TAP_MAP = TapSimplify.toJson(new TapMap());
+    public static final String TAP_ARRAY = TapSimplify.toJson(new TapArray());
 
     private static Logger log = LoggerFactory.getLogger(SchemaUtils.class);
 
@@ -186,4 +191,58 @@ public static void removeDeleteFields(List<Schema> inputSchema) {
         }
     }
 
+    public static List<Schema> removeSubFieldsWhichFromFreeSchema(DataSourceConnectionDto dataSource, List<Schema> schemas) {
+        if (!SchemaUtils.dataBaseIsFreeSchema(dataSource)) {
+            return schemas;
+        }
+        schemas.stream().filter(Objects::nonNull).forEach(SchemaUtils::removeSubFieldsWhichFromFreeSchema);
+        return schemas;
+    }
+    public static Schema removeSubFieldsWhichFromFreeSchema(DataSourceConnectionDto dataSource, Schema schema) {
+        if (SchemaUtils.dataBaseIsFreeSchema(dataSource)) {
+            return schema;
+        }
+        SchemaUtils.removeSubFieldsWhichFromFreeSchema(schema);
+        return schema;
+    }
+
+    public static boolean dataBaseIsFreeSchema(DataSourceConnectionDto dataSource) {
+        List<String> definitionTags = dataSource.getDefinitionTags();
+        return CollUtil.isNotEmpty(definitionTags) && definitionTags.contains(SCHEMA_FREE);
+    }
+
+    public static void removeSubFieldsWhichFromFreeSchema(Schema schema) {
+        List<Field> fields = schema.getFields();
+        if (CollUtil.isEmpty(fields)) {
+            return;
+        }
+        Map<String, Field> fieldMap = fields.stream()
+                .collect(Collectors.toMap(Field::getFieldName, f -> f, (f1, f2) -> f1));
+
+        List<Field> afterFilterField = fieldMap.entrySet()
+                .stream()
+                .filter(entry -> SchemaUtils.isFreeSchemaFields(fieldMap, entry))
+                .map(Map.Entry::getValue)
+                .collect(Collectors.toList());
+        //4. Otherwise, delete the current attribute
+        fields.removeAll(afterFilterField);
+    }
+
+    public static boolean isFreeSchemaFields(Map<String, Field> allFieldMap, Map.Entry<String, Field> fieldEntry) {
+        String fieldName = fieldEntry.getKey();
+        int index = fieldName.lastIndexOf(".");
+        if (index <= 0) {
+            return false;
+        }
+        //2. Traverse attribute names with a length greater than 2
+        String fatherFieldName = fieldName.substring(0, index);
+        //3. If the parent attribute does not exist, skip it
+        if (!allFieldMap.containsKey(fatherFieldName)) {
+            return false;
+        }
+        Field field = allFieldMap.get(fatherFieldName);
+        String tapType = field.getTapType();
+        //3. If the type of the parent property is not TapMap and TapArray, it will jump out
+        return TAP_MAP.equals(tapType) || TAP_ARRAY.equals(tapType);
+    }
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/util/ConnHeartbeatUtils.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/util/ConnHeartbeatUtils.java
index 822953ae5a..717e02f31f 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/util/ConnHeartbeatUtils.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/util/ConnHeartbeatUtils.java
@@ -136,7 +136,7 @@ public static DataSourceConnectionDto generateConnections(String dataSourceId, D
             this.put("initial_totals", 1);
             this.put("incremental_interval", 1000);
             this.put("incremental_interval_totals", 1);
-            this.put("incremental_types", new int[]{2});
+            this.put("incremental_types", new int[]{1});
             this.put("table_name", TABLE_NAME);
             this.put("table_fields", new ArrayList<Map<String, Object>>() {{
                 add(new HashMap<String, Object>() {{
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/error/TapDynamicTableNameExCode_35.java b/manager/tm-common/src/main/java/com/tapdata/tm/error/TapDynamicTableNameExCode_35.java
new file mode 100644
index 0000000000..75467123e6
--- /dev/null
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/error/TapDynamicTableNameExCode_35.java
@@ -0,0 +1,56 @@
+package com.tapdata.tm.error;
+
+import io.tapdata.exception.TapExClass;
+import io.tapdata.exception.TapExCode;
+import io.tapdata.exception.TapExLevel;
+import io.tapdata.exception.TapExType;
+
+/**
+ * @author gavin
+ * @create 2023-03-26 15:20
+ **/
+@TapExClass(code = 35, module = "Dynamic Table Name", prefix = "DTN", describe = "Dynamic Table Name")
+public interface TapDynamicTableNameExCode_35 {
+    @TapExCode
+    String UNKNOWN_ERROR = "35001";
+
+    @TapExCode(
+            describe = "Fail to get instantiation for dynamic table name",
+            describeCN = "无法获取动态表名的实例化对象",
+            solution = "Please select a supported dynamic table name generation rule",
+            solutionCN = "请选择一个已支持的动态表名生成规则",
+            type = TapExType.PARAMETER_INVALID,
+            level = TapExLevel.NORMAL
+    )
+    String DYNAMIC_INSTANTIATION_FAILED = "35002";
+
+    @TapExCode(
+            describe = "Fail to get invocation for dynamic table name",
+            describeCN = "无法获取对动态表名称的调用方法",
+            solution = "Please select a supported dynamic table name generation rule",
+            solutionCN = "请选择一个已支持的动态表名生成规则",
+            type = TapExType.PARAMETER_INVALID,
+            level = TapExLevel.NORMAL
+    )
+    String DYNAMIC_INVOCATION_FAILED = "35003";
+
+    @TapExCode(
+            describe = "Dynamic method not find of Dynamic table name generation rule",
+            describeCN = "找不到动态表名生成规则的动态方法",
+            solution = "Please select a supported dynamic table name generation rule",
+            solutionCN = "请选择一个已支持的动态表名生成规则",
+            type = TapExType.PARAMETER_INVALID,
+            level = TapExLevel.NORMAL
+    )
+    String UN_SUPPORT_DYNAMIC_RULE = "35004";
+
+    @TapExCode(
+            describe = "Can not access dynamic table name generation method",
+            describeCN = "无法访问动态表名生成方法",
+            solution = "Please select a supported dynamic table name generation rule",
+            solutionCN = "请选择一个已支持的动态表名生成规则",
+            type = TapExType.PARAMETER_INVALID,
+            level = TapExLevel.NORMAL
+    )
+    String DYNAMIC_METHOD_ACCESS_FAILED = "35005";
+}
diff --git a/manager/tm-sdk/pom.xml b/manager/tm-sdk/pom.xml
index 9636971f79..2e849fb384 100644
--- a/manager/tm-sdk/pom.xml
+++ b/manager/tm-sdk/pom.xml
@@ -22,7 +22,7 @@
 		<dependency>
 			<groupId>io.tapdata</groupId>
 			<artifactId>tapdata-api</artifactId>
-			<version>1.3.5-SNAPSHOT</version>
+			<version>1.3.6-SNAPSHOT</version>
 		</dependency>
 
     <dependency>
diff --git a/manager/tm/src/main/java/com/tapdata/tm/init/patches/daas/v3_5_16_Heartbeat_Dummy_Connection.java b/manager/tm/src/main/java/com/tapdata/tm/init/patches/daas/v3_5_16_Heartbeat_Dummy_Connection.java
new file mode 100644
index 0000000000..63045f3322
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/init/patches/daas/v3_5_16_Heartbeat_Dummy_Connection.java
@@ -0,0 +1,51 @@
+package com.tapdata.tm.init.patches.daas;
+
+import com.tapdata.tm.commons.util.ConnHeartbeatUtils;
+import com.tapdata.tm.ds.entity.DataSourceEntity;
+import com.tapdata.tm.ds.repository.DataSourceRepository;
+import com.tapdata.tm.init.PatchType;
+import com.tapdata.tm.init.PatchVersion;
+import com.tapdata.tm.init.patches.AbsPatch;
+import com.tapdata.tm.init.patches.PatchAnnotation;
+import com.tapdata.tm.utils.MapUtils;
+import com.tapdata.tm.utils.SpringContextHelper;
+import io.tapdata.utils.AppType;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+@PatchAnnotation(appType = AppType.DAAS, version = "3.5-16")
+public class v3_5_16_Heartbeat_Dummy_Connection extends AbsPatch {
+	private DataSourceRepository dataSourceRepository;
+
+	public v3_5_16_Heartbeat_Dummy_Connection(PatchType type, PatchVersion version) {
+		super(type, version);
+		dataSourceRepository = SpringContextHelper.getBean(DataSourceRepository.class);
+	}
+
+	@Override
+	public void run() {
+		Query query = Query.query(Criteria.where("name").is(ConnHeartbeatUtils.CONNECTION_NAME));
+		List<DataSourceEntity> connections = dataSourceRepository.findAll(query);
+		if (CollectionUtils.isEmpty(connections)) {
+			return;
+		}
+		for (DataSourceEntity conn : connections) {
+			dataSourceRepository.decryptConfig(conn);
+			Map<String, Object> config = conn.getConfig();
+			if (MapUtils.isEmpty(config) || !ConnHeartbeatUtils.MODE.equals(config.get("mode"))) {
+				continue;
+			}
+			config.put("incremental_types", new int[]{1});
+			dataSourceRepository.encryptConfig(conn);
+			query = Query.query(Criteria.where("_id").is(conn.getId()));
+			Update update = Update.update("encryptConfig", conn.getEncryptConfig());
+			dataSourceRepository.update(query, update);
+		}
+	}
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/init/patches/dfs/v3_5_4_Heartbeat_Dummy_Connection.java b/manager/tm/src/main/java/com/tapdata/tm/init/patches/dfs/v3_5_4_Heartbeat_Dummy_Connection.java
new file mode 100644
index 0000000000..bf1e5dbaf7
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/init/patches/dfs/v3_5_4_Heartbeat_Dummy_Connection.java
@@ -0,0 +1,50 @@
+package com.tapdata.tm.init.patches.dfs;
+
+import com.tapdata.tm.commons.util.ConnHeartbeatUtils;
+import com.tapdata.tm.ds.entity.DataSourceEntity;
+import com.tapdata.tm.ds.repository.DataSourceRepository;
+import com.tapdata.tm.init.PatchType;
+import com.tapdata.tm.init.PatchVersion;
+import com.tapdata.tm.init.patches.AbsPatch;
+import com.tapdata.tm.init.patches.PatchAnnotation;
+import com.tapdata.tm.utils.MapUtils;
+import com.tapdata.tm.utils.SpringContextHelper;
+import io.tapdata.utils.AppType;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+
+import java.util.List;
+import java.util.Map;
+
+@PatchAnnotation(appType = AppType.DFS, version = "3.5-4")
+public class v3_5_4_Heartbeat_Dummy_Connection extends AbsPatch {
+	private DataSourceRepository dataSourceRepository;
+
+	public v3_5_4_Heartbeat_Dummy_Connection(PatchType type, PatchVersion version) {
+		super(type, version);
+		dataSourceRepository = SpringContextHelper.getBean(DataSourceRepository.class);
+	}
+
+	@Override
+	public void run() {
+		Query query = Query.query(Criteria.where("name").is(ConnHeartbeatUtils.CONNECTION_NAME));
+		List<DataSourceEntity> connections = dataSourceRepository.findAll(query);
+		if (CollectionUtils.isEmpty(connections)) {
+			return;
+		}
+		for (DataSourceEntity conn : connections) {
+			dataSourceRepository.decryptConfig(conn);
+			Map<String, Object> config = conn.getConfig();
+			if (MapUtils.isEmpty(config) || !ConnHeartbeatUtils.MODE.equals(config.get("mode"))) {
+				continue;
+			}
+			config.put("incremental_types", new int[]{1});
+			dataSourceRepository.encryptConfig(conn);
+			query = Query.query(Criteria.where("_id").is(conn.getId()));
+			Update update = Update.update("encryptConfig", conn.getEncryptConfig());
+			dataSourceRepository.update(query, update);
+		}
+	}
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java
index 325a093b84..7ed8391567 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java
@@ -139,7 +139,7 @@ protected void beforeSave(MetadataInstancesDto record, UserDetail user) {
                 }
             }
         }
-
+        if (null == record) return;
         beforeCreateOrUpdate(record, user);
     }
 
@@ -406,7 +406,7 @@ public void beforeCreateOrUpdate(MetadataInstancesDto data, UserDetail user) {
     }
 
 
-    private void afterFindById(MetadataInstancesDto result) {
+    protected void afterFindById(MetadataInstancesDto result) {
         if (result != null && CollectionUtils.isNotEmpty(result.getFields())) {
             List<Field> fields = result.getFields();
             for (Field field : fields) {
@@ -1396,7 +1396,7 @@ public Map<String, String> findKVByNode(String nodeId) {
         return kv;
     }
 
-    private Map<String, String> getNodeMapping(UserDetail user, TaskDto taskDto, Map<String, String> kv, Node node) {
+    protected Map<String, String> getNodeMapping(UserDetail user, TaskDto taskDto, Map<String, String> kv, Node node) {
         if (node instanceof ProcessorNode) {
             kv.put(node.getId(), getQualifiedNameByNodeId(node, user, null, null, taskDto.getId().toHexString()));
             if (node instanceof MergeTableNode) {
diff --git a/manager/tm/src/main/java/com/tapdata/tm/schedule/TaskRestartSchedule.java b/manager/tm/src/main/java/com/tapdata/tm/schedule/TaskRestartSchedule.java
index 8f8fe2917e..22f04a02ff 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/schedule/TaskRestartSchedule.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/schedule/TaskRestartSchedule.java
@@ -14,6 +14,7 @@
 import com.tapdata.tm.statemachine.service.StateMachineService;
 import com.tapdata.tm.task.service.TaskScheduleService;
 import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.task.service.TransformSchemaService;
 import com.tapdata.tm.user.service.UserService;
 import com.tapdata.tm.utils.FunctionUtils;
 import com.tapdata.tm.utils.MongoUtils;
@@ -55,6 +56,7 @@ public class TaskRestartSchedule {
     private WorkerService workerService;
     private MonitoringLogsService monitoringLogsService;
     private StateMachineService stateMachineService;
+    private TransformSchemaService transformSchema;
 
     /**
      * 定时重启任务，只要找到有重启标记，并且是停止状态的任务，就重启，每分钟启动一次
@@ -72,6 +74,7 @@ public void restartTask() {
 
             try {
                 UserDetail user = userService.loadUserById(MongoUtils.toObjectId(task.getRestartUserId()));
+                transformSchema.transformSchemaBeforeDynamicTableName(task, user);
                 taskService.start(task.getId(), user, true);
             } catch (Exception e) {
                 log.warn("restart subtask error, task id = {}, e = {}", task.getId(), e.getMessage());
@@ -132,6 +135,7 @@ public void engineRestartNeedStartTask() {
 
             StateMachineResult stateMachineResult = stateMachineService.executeAboutTask(taskDto, DataFlowEvent.OVERTIME, user);
             if (stateMachineResult.isOk()) {
+                transformSchema.transformSchemaBeforeDynamicTableName(taskDto, user);
                 taskScheduleService.scheduling(taskDto, user);
             }
         }
@@ -231,6 +235,7 @@ public void schedulingTask() {
 
             long heartExpire = getHeartExpire();
 
+            transformSchema.transformSchemaBeforeDynamicTableName(taskDto, user);
             if (Objects.nonNull(taskDto.getSchedulingTime()) && (
                     System.currentTimeMillis() - taskDto.getSchedulingTime().getTime() > heartExpire)) {
 
diff --git a/manager/tm/src/main/java/com/tapdata/tm/schedule/service/ScheduleService.java b/manager/tm/src/main/java/com/tapdata/tm/schedule/service/ScheduleService.java
index 3e545ce8b7..e7f0ce14c3 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/schedule/service/ScheduleService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/schedule/service/ScheduleService.java
@@ -8,6 +8,7 @@
 import com.tapdata.tm.task.entity.TaskRecord;
 import com.tapdata.tm.task.service.TaskRecordService;
 import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.task.service.TransformSchemaService;
 import com.tapdata.tm.user.service.UserService;
 import com.tapdata.tm.utils.Lists;
 import com.tapdata.tm.utils.MongoUtils;
@@ -33,6 +34,8 @@ public class ScheduleService {
 
 	@Autowired
 	private TaskRecordService taskRecordService;
+    @Autowired
+    private TransformSchemaService transformSchema;
 
     public void executeTask(TaskDto taskDto) {
         WorkerService workerService = SpringContextHelper.getBean(WorkerService.class);
@@ -106,6 +109,7 @@ public void executeTask(TaskDto taskDto) {
 						taskDto.setCrontabScheduleMsg("");
 						taskService.save(taskDto, userDetail);
 						// 执行记录
+                        transformSchema.transformSchemaBeforeDynamicTableName(taskDto, userDetail);
 						taskService.start(taskDto.getId(), userDetail, true);
 					} else if (TaskDto.TYPE_INITIAL_SYNC_CDC.equals(taskDto.getType()) && TaskDto.STATUS_RUNNING.equals(status)) {
                 CompletableFuture<String> pause = CompletableFuture.supplyAsync(() -> {
@@ -122,6 +126,7 @@ public void executeTask(TaskDto taskDto) {
                 });
                 CompletableFuture<String> start = renew.thenCompose(result -> CompletableFuture.supplyAsync(() -> {
                     performTaskWithSpin(taskId, TaskDto.STATUS_WAIT_START, taskService);
+                    transformSchema.transformSchemaBeforeDynamicTableName(taskDto, userDetail);
                     taskService.start(taskId, userDetail, true);
                     return "ok";
                 })).exceptionally(ex -> {
@@ -138,6 +143,7 @@ public void executeTask(TaskDto taskDto) {
                 });
                 CompletableFuture<String> start = renew.thenCompose(result -> CompletableFuture.supplyAsync(() -> {
                     performTaskWithSpin(taskId, TaskDto.STATUS_WAIT_START, taskService);
+                    transformSchema.transformSchemaBeforeDynamicTableName(taskDto, userDetail);
                     taskService.start(taskId, userDetail, true);
                     return "ok";
                 })).exceptionally(ex -> {
@@ -147,6 +153,7 @@ public void executeTask(TaskDto taskDto) {
 
                 start.join();
             } else if (TaskDto.STATUS_WAIT_START.equals(status)) {
+                transformSchema.transformSchemaBeforeDynamicTableName(taskDto, userDetail);
                 taskService.start(taskId, userDetail, true);
             } else {
                 log.warn("other status can not run, need check taskId:{} status:{}", taskId, status);
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
index ca6f960114..f11cb0b945 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
@@ -156,7 +156,73 @@
 @Slf4j
 @Setter(onMethod_ = {@Autowired})
 public class TaskServiceImpl extends TaskService{
-    protected static final String CATALOG = "catalog";
+    public static final String USER_ID = "user_id";
+    public static final String COLLECTION_ID = "collectionId";
+    protected static final String PROCESSOR_THREAD_NUM="processorThreadNum";
+    protected static final String CATALOG="catalog";
+    protected static final String ELEMENT_TYEP="elementType";
+    protected static final String PROCESSOR="processor";
+    public static final String RM_ID_KEY = "rm_id";
+    public static final String SYNC_TYPE = "syncType";
+    public static final String ILLEGAL_ARGUMENT = "IllegalArgument";
+    public static final String INITIAL_SYNC_CDC = "initial_sync+cdc";
+    public static final String QUALIFIED_NAME = "qualified_name";
+    public static final String FIELDS = "fields";
+    public static final String IS_DELETED = "is_deleted";
+    public static final String THE_COPIED_TASK_DOES_NOT_EXIST = "The copied task does not exist";
+    public static final String CRONTAB_EXPRESSION_FLAG = "crontabExpressionFlag";
+    public static final String CONNECTION_ID = "connectionId";
+    public static final String TM_CURRENT_TIME = "tmCurrentTime";
+    public static final String PLAN_START_DATE_FLAG = "planStartDateFlag";
+    public static final String IS_PRIMARY_KEY = "isPrimaryKey";
+    public static final String INITIAL_SYNC = "initial_sync";
+    public static final String CURRENT_EVENT_TIMESTAMP = "currentEventTimestamp";
+    public static final String ATTRS = "attrs";
+    public static final String SYNC_POINTS = "syncPoints";
+    public static final String DAG_NODES = "dag.nodes";
+    public static final String REGEX = "$regex";
+    public static final String DELETE_TASK_HANDLE_EXCEPTION_ERROR_TASK_ID = "delete task, handle exception error, task id = {}";
+    public static final String TOTAL = "total";
+    public static final String CONNECTION_NAME = "connectionName";
+    public static final String TABLE = "table";
+    public static final String TASK_LIST_WARN_MESSAGE = "Task.ListWarnMessage";
+    public static final String EDGE_MILESTONES = "edgeMilestones";
+    public static final String SYNC_PROGRESS = "syncProgress";
+    public static final String TASK_NOT_FOUND = "Task.NotFound";
+    public static final String STATUS = "status";
+    public static final String CREATE_TIME = "createTime";
+    public static final String DAG_NODES_CONNECTION_ID = "dag.nodes.connectionId";
+    public static final String TABLE_NAME = "tableName";
+    public static final String COUNT = "count";
+    public static final String DATETIME_PATTERN = "yyyyMMdd";
+    public static final String METADATA_INSTANCES = "MetadataInstances";
+    public static final String CHILDREN = "children";
+    public static final String TARGET_PATH = "targetPath";
+    public static final String FIELD = "field";
+    public static final String COLLECTIONS = "collections";
+    public static final String RELATIONSHIPS = "relationships";
+    public static final String TASK_IMPORT_FORMAT_ERROR = "Task.ImportFormatError";
+    public static final String START_TIME = "startTime";
+    public static final String STOP_RETRY_TIMES = "stopRetryTimes";
+    public static final String SCHEDULE_DATE = "scheduleDate";
+    public static final String FUNCTION_RETRY_STATUS = "functionRetryStatus";
+    public static final String TASK_ID = "taskId";
+    public static final String MAPPINGS = "mappings";
+    public static final String TASK_RECORD_ID = "taskRecordId";
+    public static final String RESTART_FLAG = "restartFlag";
+    public static final String AGENT_ID = "agentId";
+    public static final String SOURCE = "source";
+    public static final String EMBEDDED_PATH = "embeddedPath";
+    public static final String COLUMN = "column";
+    public static final String STOPED_DATE = "stopedDate";
+    public static final String TARGET = "target";
+
+    @NotNull
+    private static String getTableName() {
+        return TABLE_NAME;
+    }
+    protected static final String SHARE_CDC_STOP = "shareCdcStop";
+    protected static final String SHARE_CDC_STOP_MESSAGE = "shareCdcStopMessage";
     private MessageServiceImpl messageService;
     private SnapshotEdgeProgressService snapshotEdgeProgressService;
     private InspectService inspectService;
@@ -227,8 +293,8 @@ public TaskServiceImpl(@NonNull TaskRepository repository) {
 	public Supplier<TaskDto> dataPermissionFindById(ObjectId taskId, Field fields) {
 		return () -> {
 			if (null != fields) {
-				fields.put("user_id", true);
-				fields.put("syncType", true);
+				fields.put(USER_ID, true);
+				fields.put(SYNC_TYPE, true);
 				fields.put(DataPermissionHelper.FIELD_NAME, true);
 				fields.put(ConnHeartbeatUtils.TASK_RELATION_FIELD, true);
 			}
@@ -301,17 +367,7 @@ public TaskDto create(TaskDto taskDto, UserDetail user) {
             if (TaskDto.SYNC_TYPE_MIGRATE.equals(taskDto.getSyncType())) {
                 transformSchemaAsyncService.transformSchema(dag, user, taskDto.getId());
             } else {
-                transformSchemaService.transformSchema(dag, user, taskDto.getId());
-                //暂时先这样子更新，为了保存join节点中的推演中产生的primarykeys数据
-                long count = dag.getNodes().stream()
-                        .filter(n -> NodeEnum.join_processor.name().equals(n.getType()))
-                        .count();
-
-                if (count != 0) {
-                    Update update = new Update();
-                    update.set("dag", taskDto.getDag());
-                    updateById(taskDto.getId(), update, user);
-                }
+                transformSchemaService.transformSchemaAndUpdateTask(taskDto, user);
             }
         }
 
@@ -377,7 +433,7 @@ private void setDefault(TaskDto taskDto) {
         taskDto.setShareCdcEnable(getBoolValue(taskDto.getShareCdcEnable(), false));
 
         // 类型 [{label: '全量+增量', value: 'initial_sync+cdc'}, {label: '全量', value: 'initial_sync'}, {label: '增量', value: 'cdc'} ]
-        taskDto.setType(getStringValue(taskDto.getType(), "initial_sync+cdc"));
+        taskDto.setType(getStringValue(taskDto.getType(), INITIAL_SYNC_CDC));
 
         // 目标写入线程数
         taskDto.setWriteThreadSize(getIntValue(taskDto.getWriteThreadSize(), 8));
@@ -389,17 +445,6 @@ private void setDefault(TaskDto taskDto) {
         taskDto.setStatuses(new ArrayList<>());
     }
 
-    private void handleMessage(TaskDto taskDto, Map<String, List<Message>> validateMessage) {
-        log.debug("handle error message, task id = {}", taskDto.getId());
-        if (validateMessage.size() != 0) {
-            List<Message> messages = new ArrayList<>();
-            for (List<Message> value : validateMessage.values()) {
-                messages.addAll(value);
-            }
-            taskDto.setMessage(messages);
-        }
-    }
-
     protected void beforeSave(TaskDto task, UserDetail user) {
         //setDefault(task);
 
@@ -574,81 +619,6 @@ public TaskDto updateAfter(TaskDto taskDto, UserDetail user) {
         return save(taskDto, user);
     }
 
-    private void supplementMigrateFieldMapping(TaskDto taskDto, UserDetail userDetail) {
-        if (!TaskDto.SYNC_TYPE_MIGRATE.equals(taskDto.getSyncType())) {
-            return;
-        }
-
-        DAG dag = taskDto.getDag();
-        if (Objects.isNull(dag) || CollectionUtils.isEmpty(dag.getNodes())) {
-            return;
-        }
-
-        dag.getNodes().forEach(node -> {
-            if (node instanceof MigrateFieldRenameProcessorNode) {
-                MigrateFieldRenameProcessorNode fieldNode = (MigrateFieldRenameProcessorNode) node;
-                LinkedList<TableFieldInfo> fieldsMapping = fieldNode.getFieldsMapping();
-
-                if (CollectionUtils.isNotEmpty(fieldsMapping)) {
-                    List<String> tableNames = fieldsMapping.stream()
-                            .map(TableFieldInfo::getOriginTableName)
-                            .collect(Collectors.toList());
-                    LinkedList<Node<?>> preNodes = node.getDag().getPreNodes(node.getId());
-                    if (CollectionUtils.isEmpty(preNodes)) {
-                        return;
-                    }
-                    Node previousNode = preNodes.getLast();
-
-                    List<MetadataInstancesDto> metaList = metadataInstancesService.findByNodeId(previousNode.getId(),
-                            userDetail, taskDto.getId().toHexString(), "qualified_name", "fields");
-                    Map<String, List<com.tapdata.tm.commons.schema.Field>> fieldMap = metaList.stream()
-                            .collect(Collectors.toMap(MetadataInstancesDto::getAncestorsName, MetadataInstancesDto::getFields));
-                    fieldsMapping.forEach(table -> {
-                        Operation operation = table.getOperation();
-                        LinkedList<FieldInfo> fields = table.getFields();
-
-                        List<String> fieldNames = Lists.newArrayList();
-                        if (CollectionUtils.isNotEmpty(fields)) {
-                            fieldNames = fields.stream().map(FieldInfo::getSourceFieldName).collect(Collectors.toList());
-                        }
-
-                        List<String> hiddenFields = table.getFields().stream().filter(t -> !t.getIsShow())
-                                .map(FieldInfo::getSourceFieldName)
-                                .collect(Collectors.toList());
-
-                        List<com.tapdata.tm.commons.schema.Field> tableFields = fieldMap.get(table.getOriginTableName());
-                        if (CollectionUtils.isNotEmpty(tableFields)) {
-                            for (com.tapdata.tm.commons.schema.Field field : tableFields) {
-                                String targetFieldName = field.getFieldName();
-                                if (!fieldNames.contains(targetFieldName)) {
-                                    if (CollectionUtils.isNotEmpty(hiddenFields) && hiddenFields.contains(targetFieldName)) {
-                                        continue;
-                                    }
-
-                                    if (StringUtils.isNotBlank(operation.getPrefix())) {
-                                        targetFieldName = operation.getPrefix().concat(targetFieldName);
-                                    }
-                                    if (StringUtils.isNotBlank(operation.getSuffix())) {
-                                        targetFieldName = targetFieldName.concat(operation.getSuffix());
-                                    }
-                                    if (StringUtils.isNotBlank(operation.getCapitalized())) {
-                                        if (CapitalizedEnum.fromValue(operation.getCapitalized()) == CapitalizedEnum.UPPER) {
-                                            targetFieldName = StringUtils.upperCase(targetFieldName);
-                                        } else {
-                                            targetFieldName = StringUtils.lowerCase(targetFieldName);
-                                        }
-                                    }
-                                    FieldInfo fieldInfo = new FieldInfo(field.getFieldName(), targetFieldName, true, "system");
-                                    fields.add(fieldInfo);
-                                }
-                            }
-                        }
-                    });
-                }
-            }
-        });
-    }
-
 
     public TaskDto updateShareCacheTask(String id, SaveShareCacheParam saveShareCacheParam, UserDetail user) {
         TaskDto taskDto = findById(MongoUtils.toObjectId(id));
@@ -676,7 +646,7 @@ public void checkTaskName(String newName, UserDetail user, ObjectId id) {
 
     public boolean checkTaskNameNotError(String newName, UserDetail user, ObjectId id) {
 
-        Criteria criteria = Criteria.where("name").is(newName).and("is_deleted").ne(true);
+        Criteria criteria = Criteria.where("name").is(newName).and(IS_DELETED).ne(true);
         if (id != null) {
             criteria.and("_id").ne(id);
         }
@@ -725,9 +695,20 @@ public TaskDto confirmById(TaskDto taskDto, UserDetail user, boolean confirm) {
 
         //saveInspect(existedTask, taskDto, user);
 
+        checkShareCdcStatus(taskDto, user);
+
         return confirmById(taskDto, user, confirm, false);
     }
 
+    protected void checkShareCdcStatus(TaskDto taskDto,UserDetail user){
+        if( null != taskDto.getShareCdcEnable() && !taskDto.getShareCdcEnable() &&  null != taskDto.getShareCdcStop() && StringUtils.isNotBlank(taskDto.getShareCdcStopMessage())){
+            Update set = new Update();
+            set.unset(SHARE_CDC_STOP).unset(SHARE_CDC_STOP_MESSAGE);
+            Criteria criteriaTask = Criteria.where("_id").is(taskDto.getId());
+            update(new Query(criteriaTask), set,user);
+        }
+    }
+
     protected void checkDDLConflict(TaskDto taskDto) {
         LinkedList<DatabaseNode> sourceNode = taskDto.getDag().getSourceNode();
         if (CollectionUtils.isNotEmpty(sourceNode)) {
@@ -756,23 +737,11 @@ public void checkTaskInspectFlag (TaskDto taskDto) {
 //        }
     }
 
-    private void saveInspect(TaskDto existedTask, TaskDto taskDto, UserDetail userDetail) {
-        try {
-            ObjectId taskId = taskDto.getId();
-            if (isInspectPropertyChanged(existedTask, taskDto)) {
-                inspectService.deleteByTaskId(taskId.toString());
-                inspectService.saveInspect(taskDto, userDetail);
-            }
-        } catch (Exception e) {
-            log.error("新建校验任务出错 {}", e.getMessage());
-        }
-    }
-
 
     //编辑一个复制，要根据属性判断是否删除原来的inspect
     //源数据库或者目标数据库变动，要删除
     //源表或者目标秒变动，要删除
-    private Boolean isInspectPropertyChanged(TaskDto existedTask, TaskDto newTask) {
+    protected Boolean isInspectPropertyChanged(TaskDto existedTask, TaskDto newTask) {
         Boolean changed = false;
 
         DatabaseNode existedSourceDataNode = (DatabaseNode) getSourceNode(existedTask);
@@ -782,6 +751,10 @@ private Boolean isInspectPropertyChanged(TaskDto existedTask, TaskDto newTask) {
         DatabaseNode newSourceDataNode = (DatabaseNode) getSourceNode(newTask);
         DatabaseNode newTargetDataNode = (DatabaseNode) getTargetNode(newTask);
 
+        if (null == existedSourceDataNode || null == existedTargetDataNode ||
+                null == newSourceDataNode || null == newTargetDataNode){
+            throw new BizException(ILLEGAL_ARGUMENT,"dataNode");
+        }
         if (!existedSourceDataNode.getName().equals(newSourceDataNode.getName()) ||
                 !existedTargetDataNode.getName().equals(newTargetDataNode.getName())
         ) {
@@ -789,8 +762,8 @@ private Boolean isInspectPropertyChanged(TaskDto existedTask, TaskDto newTask) {
         } else {
             List<SyncObjects> newSyncObjects = newTargetDataNode.getSyncObjects();
             List<SyncObjects> existedSyncObjects = existedTargetDataNode.getSyncObjects();
-            Optional<SyncObjects> newTableSyncObject = newSyncObjects.stream().filter(e -> "table".equals(e.getType())).findFirst();
-            Optional<SyncObjects> existedTableSyncObject = existedSyncObjects.stream().filter(e -> "table".equals(e.getType())).findFirst();
+            Optional<SyncObjects> newTableSyncObject = newSyncObjects.stream().filter(e -> TABLE.equals(e.getType())).findFirst();
+            Optional<SyncObjects> existedTableSyncObject = existedSyncObjects.stream().filter(e -> TABLE.equals(e.getType())).findFirst();
 
             if (existedTableSyncObject.isPresent() && newTableSyncObject.isPresent()) {
                 List<String> existedSourceTableNames = existedTableSyncObject.get().getObjectNames();
@@ -825,7 +798,7 @@ public TaskDto confirmById(TaskDto taskDto, UserDetail user, boolean confirm, bo
             if (!importTask) {
                 Map<String, List<Message>> validateMessage = dag.validate();
                 if (!validateMessage.isEmpty()) {
-                    throw new BizException("Task.ListWarnMessage", validateMessage);
+                    throw new BizException(TASK_LIST_WARN_MESSAGE, validateMessage);
                 }
             }
         }
@@ -883,7 +856,7 @@ public void checkDagAgentConflict(TaskDto taskDto, UserDetail user, boolean show
         }
         if (!validateMessage.isEmpty()) {
             if (showListMsg) {
-                throw new BizException("Task.ListWarnMessage", validateMessage);
+                throw new BizException(TASK_LIST_WARN_MESSAGE, validateMessage);
             } else {
                 Message message = validateMessage.values().iterator().next().get(0);
                 throw new BizException(message.getCode(), message.getMsg());
@@ -979,7 +952,7 @@ public void afterRemove(TaskDto taskDto, UserDetail user) {
         //将任务删除标识改成true
         ObjectId id = taskDto.getId();
         Update update = resetUpdate();
-        update.set("is_deleted", true);
+        update.set(IS_DELETED, true);
         update(new Query(Criteria.where("_id").is(id)), update);
 
         //delete AutoInspectResults
@@ -1017,7 +990,7 @@ public void deleteShareCache(ObjectId id, UserDetail user) {
             log.error("停止异常，但是共享缓存仍然删除 {}", e.getMessage());
         }
         //将任务删除标识改成true
-        update(new Query(Criteria.where("_id").is(id)), Update.update("is_deleted", true));
+        update(new Query(Criteria.where("_id").is(id)), Update.update(IS_DELETED, true));
 //        remove(id, user);
     }
 
@@ -1139,8 +1112,8 @@ public TaskDto copy(ObjectId id, UserDetail user) {
         }
         Map<String, Object> attrs = taskDto.getAttrs();
         if (null != attrs) {
-            attrs.remove("edgeMilestones");
-            attrs.remove("syncProgress");
+            attrs.remove(EDGE_MILESTONES);
+            attrs.remove(SYNC_PROGRESS);
         }
         //taskDto.setTemp(null);
         if(!checkCloudTaskLimit(id,user,false)){
@@ -1238,7 +1211,9 @@ public void renew(ObjectId id, UserDetail user, boolean system) {
                 Update update = Update.update(TaskDto.LASTTASKRECORDID, lastTaskRecordId);
                 updateById(id.toHexString(), update, user);
 
-                taskSnapshot.setTaskRecordId(lastTaskRecordId);
+                if (null != taskSnapshot){
+                    taskSnapshot.setTaskRecordId(lastTaskRecordId);
+                }
                 disruptorService.sendMessage(DisruptorTopicEnum.CREATE_RECORD,
                         new TaskRecord(lastTaskRecordId, taskDto.getId().toHexString(), taskSnapshot, system ? "system" : user.getUserId(), new Date()));
             }
@@ -1285,7 +1260,7 @@ public void afterRenew(TaskDto taskDto, UserDetail user) {
     public TaskDto checkExistById(ObjectId id, UserDetail user) {
         TaskDto taskDto = findById(id, user);
         if (taskDto == null) {
-            throw new BizException("Task.NotFound", "The copied task does not exist");
+            throw new BizException(TASK_NOT_FOUND, THE_COPIED_TASK_DOES_NOT_EXIST);
         }
 
         return taskDto;
@@ -1305,7 +1280,7 @@ public TaskDto checkExistById(ObjectId id, UserDetail user, String... fields) {
         }
         TaskDto taskDto = findOne(query, user);
         if (taskDto == null) {
-            throw new BizException("Task.NotFound", "The copied task does not exist");
+            throw new BizException(TASK_NOT_FOUND, THE_COPIED_TASK_DOES_NOT_EXIST);
         }
 
         return taskDto;
@@ -1324,7 +1299,7 @@ public TaskDto checkExistById(ObjectId id, String... fields) {
         }
         TaskDto taskDto = findOne(query);
         if (taskDto == null) {
-            throw new BizException("Task.NotFound", "The copied task does not exist");
+            throw new BizException(TASK_NOT_FOUND, THE_COPIED_TASK_DOES_NOT_EXIST);
         }
 
         return taskDto;
@@ -1361,7 +1336,7 @@ public List<MutiResponseMessage> batchStart(List<ObjectId> taskIds, UserDetail u
                         mutiResponseMessage.setCode(responseMessage.getCode());
                         mutiResponseMessage.setMessage(responseMessage.getMessage());
                     } catch (Throwable ex) {
-                        log.warn("delete task, handle exception error, task id = {}", task.getId().toHexString());
+                        log.warn(DELETE_TASK_HANDLE_EXCEPTION_ERROR_TASK_ID, task.getId().toHexString());
                     }
                 }
             }
@@ -1372,7 +1347,7 @@ public List<MutiResponseMessage> batchStart(List<ObjectId> taskIds, UserDetail u
     }
 
     public int subCronOrPlanNum(TaskDto task, int runningNum) {
-        TaskDto taskScheduleFlag = findByTaskId(task.getId(), "planStartDateFlag", "crontabExpressionFlag");
+        TaskDto taskScheduleFlag = findByTaskId(task.getId(), PLAN_START_DATE_FLAG, CRONTAB_EXPRESSION_FLAG);
         if (checkIsCronOrPlanTask(taskScheduleFlag) && runningNum > 0) {
             runningNum -= 1;
         }
@@ -1408,7 +1383,7 @@ public List<MutiResponseMessage> batchStop(List<ObjectId> taskIds, UserDetail us
                         mutiResponseMessage.setCode(responseMessage.getCode());
                         mutiResponseMessage.setMessage(responseMessage.getMessage());
                     } catch (Throwable ex) {
-                        log.warn("delete task, handle exception error, task id = {}",  taskId.toHexString());
+                        log.warn(DELETE_TASK_HANDLE_EXCEPTION_ERROR_TASK_ID,  taskId.toHexString());
                     }
                 }
             }
@@ -1447,7 +1422,7 @@ public List<MutiResponseMessage> batchDelete(List<ObjectId> taskIds, UserDetail
                         mutiResponseMessage.setCode(responseMessage.getCode());
                         mutiResponseMessage.setMessage(responseMessage.getMessage());
                     } catch (Throwable ex) {
-                        log.warn("delete task, handle exception error, task id = {}",  taskId.toHexString());
+                        log.warn(DELETE_TASK_HANDLE_EXCEPTION_ERROR_TASK_ID,  taskId.toHexString());
                     }
                 }
             }
@@ -1478,7 +1453,7 @@ public List<MutiResponseMessage> batchRenew(List<ObjectId> taskIds, UserDetail u
                         mutiResponseMessage.setCode(responseMessage.getCode());
                         mutiResponseMessage.setMessage(responseMessage.getMessage());
                     } catch (Throwable ex) {
-                        log.warn("delete task, handle exception error, task id = {}",  taskId.toHexString());
+                        log.warn(DELETE_TASK_HANDLE_EXCEPTION_ERROR_TASK_ID,  taskId.toHexString());
                     }
                 }
             }
@@ -1509,17 +1484,17 @@ public Page<TaskDto> find(Filter filter, UserDetail userDetail) {
             where = new Where();
             filter.setWhere(where);
         }
-        if (where.get("status") == null) {
+        if (where.get(STATUS) == null) {
             Document statusCondition = new Document();
             statusCondition.put("$nin", Lists.of(TaskDto.STATUS_DELETE_FAILED, TaskDto.STATUS_DELETING));
-            where.put("status", statusCondition);
+            where.put(STATUS, statusCondition);
         }
         //过滤掉挖掘任务
-        String syncType = (String) where.get("syncType");
+        String syncType = (String) where.get(SYNC_TYPE);
         if (StringUtils.isBlank(syncType)) {
             Document logCollectorFilter = new Document();
             logCollectorFilter.put("$nin", Lists.of(TaskDto.SYNC_TYPE_LOG_COLLECTOR, TaskDto.SYNC_TYPE_CONN_HEARTBEAT));
-            where.put("syncType", logCollectorFilter);
+            where.put(SYNC_TYPE, logCollectorFilter);
         }
 
         //过滤调共享缓存任务
@@ -1528,17 +1503,17 @@ public Page<TaskDto> find(Filter filter, UserDetail userDetail) {
         where.put("shareCache", notShareCache);
 
 
-        Boolean deleted = (Boolean) where.get("is_deleted");
+        Boolean deleted = (Boolean) where.get(IS_DELETED);
         if (deleted == null) {
             Document document = new Document();
             document.put("$ne", true);
-            where.put("is_deleted", document);
+            where.put(IS_DELETED, document);
         }
 
         Page<TaskDto> taskDtoPage = new Page<>();
         List<TaskDto> items = new ArrayList<>();
-        if (where.get("syncType") != null && (where.get("syncType") instanceof String)) {
-            String synType = (String) where.get("syncType");
+        if (where.get(SYNC_TYPE) != null && (where.get(SYNC_TYPE) instanceof String)) {
+            String synType = (String) where.get(SYNC_TYPE);
             if (SyncType.MIGRATE.getValue().equals(synType)) {
                 taskDtoPage = findDataCopyList(filter, userDetail);
             } else if (SyncType.SYNC.getValue().equals(synType)) {
@@ -1586,7 +1561,12 @@ public Page<TaskDto> find(Filter filter, UserDetail userDetail) {
                             total += dto.getTotal();
                             finished += dto.getFinished();
                         }
-                        double process = finished / (total * 1d);
+                        double process;
+                        if (total == 0){
+                            process = 0;
+                        }else {
+                            process = finished / (total * 1d);
+                        }
                         if (process > 1) {
                             process = 1;
                         }
@@ -1672,7 +1652,7 @@ public void deleteNotifyEnumData(List<TaskDto> taskDtoList) {
      * @param userDetail
      * @return
      */
-    private Page<TaskDto> findDataCopyList(Filter filter, UserDetail userDetail) {
+    protected Page<TaskDto> findDataCopyList(Filter filter, UserDetail userDetail) {
         Where where = filter.getWhere();
 
         Criteria criteria = new Criteria();
@@ -1697,7 +1677,7 @@ private Page<TaskDto> findDataCopyList(Filter filter, UserDetail userDetail) {
         criteria.andOperator(orToCriteria);
         query.addCriteria(criteria);
 			if (!userDetail.isRoot() && !DataPermissionHelper.setFilterConditions(true, query, userDetail)) {
-					criteria.and("user_id").is(userDetail.getUserId());
+					criteria.and(USER_ID).is(userDetail.getUserId());
 			}
 
         TmPageable tmPageable = new TmPageable();
@@ -1706,7 +1686,7 @@ private Page<TaskDto> findDataCopyList(Filter filter, UserDetail userDetail) {
         tmPageable.setSize(filter.getLimit());
 
         String order = filter.getOrder() == null ? "createTime DESC" : String.valueOf(filter.getOrder());
-        String sortKey = order.contains("currentEventTimestamp") ? "currentEventTimestamp" : "createTime";
+        String sortKey = order.contains(CURRENT_EVENT_TIMESTAMP) ? CURRENT_EVENT_TIMESTAMP : CREATE_TIME;
         if (order.contains("ASC")) {
             tmPageable.setSort(Sort.by(sortKey).ascending());
         } else {
@@ -1745,7 +1725,7 @@ private Page<TaskDto> findDataCopyList(Filter filter, UserDetail userDetail) {
      * @param userDetail
      * @return
      */
-    private Page<TaskDto> findDataDevList(Filter filter, UserDetail userDetail) {
+    protected Page<TaskDto> findDataDevList(Filter filter, UserDetail userDetail) {
         Query query = repository.filterToQuery(filter);
         query.limit(100000);
         query.skip(0);
@@ -1761,7 +1741,7 @@ private Page<TaskDto> findDataDevList(Filter filter, UserDetail userDetail) {
         return taskDtoPage;
     }
 
-    private Node getSourceNode(TaskDto taskDto) {
+    protected Node getSourceNode(TaskDto taskDto) {
         DAG dag = taskDto.getDag();
         if (dag == null) {
             return null;
@@ -1782,7 +1762,7 @@ private Node getSourceNode(TaskDto taskDto) {
         return null;
     }
 
-    private Node getTargetNode(TaskDto taskDto) {
+    protected Node getTargetNode(TaskDto taskDto) {
         List<Edge> edges = taskDto.getDag().getEdges();
         if (CollectionUtils.isNotEmpty(edges)) {
             Edge edge = edges.get(0);
@@ -1818,13 +1798,6 @@ public static String printInfos(DAG dag) {
      * @return 返回共享缓存任务的id, 起止时间
      */
     public LogCollectorResult searchLogCollector(String key) {
-        Criteria criteriaConnectionIds = Criteria.where("connectionIds").elemMatch(Criteria.where("$eq").is(key));
-        Criteria criteriaTables = Criteria.where("tableNames").elemMatch(Criteria.where("$eq").is(key));
-        Criteria criteria = Criteria.where("is_deleted").is(false).and("dag.nodes").elemMatch(Criteria.where("type").is("logCollector")
-                .orOperator(criteriaConnectionIds, criteriaTables));
-
-        Query query = new Query(criteria);
-        //query.fields().include()
         return new LogCollectorResult();
     }
 
@@ -1859,13 +1832,13 @@ public TaskDto createShareCacheTask(SaveShareCacheParam saveShareCacheParam, Use
     public Page<ShareCacheVo> findShareCache(Filter filter, UserDetail userDetail) {
         Where where = filter.getWhere();
         List<String> connectionIds = new ArrayList();
-        if (null != where.get("connectionName")) {
-            Map connectionName = (Map) where.remove("connectionName");
-            String conectionNameStr = (String) connectionName.remove("$regex");
+        if (null != where.get(CONNECTION_NAME)) {
+            Map connectionName = (Map) where.remove(CONNECTION_NAME);
+            String conectionNameStr = (String) connectionName.remove(REGEX);
             connectionIds = dataSourceService.findIdByName(conectionNameStr);
             Map<String, Object> connectioIdMap = new HashMap<>();
             connectioIdMap.put("$in", connectionIds);
-            where.put("dag.nodes.connectionId", connectioIdMap);
+            where.put(DAG_NODES_CONNECTION_ID, connectioIdMap);
         }
 
 
@@ -1889,7 +1862,7 @@ public Page<ShareCacheVo> findShareCache(Filter filter, UserDetail userDetail) {
                     shareCacheVo.setTableName(tableName);
                     shareCacheVo.setCreateTime(taskDto.getCreateAt());
                     if (null != sourceNode.getAttrs()) {
-                        shareCacheVo.setFields((List<String>) sourceNode.getAttrs().get("fields"));
+                        shareCacheVo.setFields((List<String>) sourceNode.getAttrs().get(FIELDS));
                     }
 
                     if (taskDto.getCurrentEventTimestamp() != null) {
@@ -1931,6 +1904,9 @@ public ShareCacheDetailVo findShareCacheById(String id) {
         shareCacheDetailVo.setName(taskDto.getName());
         shareCacheDetailVo.setStatus(taskDto.getStatus());
         shareCacheDetailVo.setSyncStatus(taskDto.getSyncStatus());
+        if (null == sourceNode || null == targetNode){
+            throw new BizException(ILLEGAL_ARGUMENT,"sourceNode");
+        }
         String connectionId = ((DataNode) sourceNode).getConnectionId();
         DataSourceConnectionDto connectionDto = dataSourceService.findOne(Query.query(Criteria.where("id").is(connectionId)));
         if (null != connectionDto) {
@@ -1943,7 +1919,7 @@ public ShareCacheDetailVo findShareCacheById(String id) {
         shareCacheDetailVo.setCreateTime(taskDto.getCreateAt());
         shareCacheDetailVo.setCreateUser(taskDto.getCreateUser());
         if (null != sourceNode.getAttrs()) {
-            shareCacheDetailVo.setFields((List<String>) sourceNode.getAttrs().get("fields"));
+            shareCacheDetailVo.setFields((List<String>) sourceNode.getAttrs().get(FIELDS));
         }
 
         if (taskDto.getCurrentEventTimestamp() != null) {
@@ -1957,7 +1933,7 @@ public ShareCacheDetailVo findShareCacheById(String id) {
         return shareCacheDetailVo;
     }
 
-    private TaskDto parseCacheToTaskDto(SaveShareCacheParam saveShareCacheParam, TaskDto taskDto) {
+    protected TaskDto parseCacheToTaskDto(SaveShareCacheParam saveShareCacheParam, TaskDto taskDto) {
         taskDto.setStatus(TaskDto.STATUS_EDIT);
         taskDto.setSyncStatus(SyncStatus.NORMAL);
         taskDto.setType(ParentTaskDto.TYPE_CDC);
@@ -1990,17 +1966,17 @@ private TaskDto parseCacheToTaskDto(SaveShareCacheParam saveShareCacheParam, Tas
 
             Map sourceNodeMap = nodeList.get(0);
             TableNode tableNode = new TableNode();
-            tableNode.setTableName((String) sourceNodeMap.get("tableName"));
-            tableNode.setType("table");
+            tableNode.setTableName((String) sourceNodeMap.get(TABLE_NAME));
+            tableNode.setType(TABLE);
             tableNode.setDatabaseType((String) sourceNodeMap.get("databaseType"));
-            tableNode.setConnectionId((String) sourceNodeMap.get("connectionId"));
+            tableNode.setConnectionId((String) sourceNodeMap.get(CONNECTION_ID));
 
+            Field field = new Field();
+            field.put("name", true);
             String connectionName = Optional.ofNullable(tableNode.getConnectionId())
                     .map(ObjectId::new)
                     .map(connId -> {
-                        return dataSourceService.findById(connId, new Field() {{
-                            put("name", true);
-                        }});
+                        return dataSourceService.findById(connId, field);
                     }).map(DataSourceConnectionDto::getName).orElse(null);
             if (null == connectionName) {
                 throw new BizException("Datasource.NotFound");
@@ -2008,8 +1984,8 @@ private TaskDto parseCacheToTaskDto(SaveShareCacheParam saveShareCacheParam, Tas
             tableNode.setName(connectionName + "-" + tableNode.getTableName());
 
             Map<String, Object> attrs = new HashMap();
-            if (null != sourceNodeMap.get("attrs")) {
-                attrs = (Map<String, Object>) sourceNodeMap.get("attrs");
+            if (null != sourceNodeMap.get(ATTRS)) {
+                attrs = (Map<String, Object>) sourceNodeMap.get(ATTRS);
                 tableNode.setAttrs(attrs);
             }
 
@@ -2032,7 +2008,7 @@ private TaskDto parseCacheToTaskDto(SaveShareCacheParam saveShareCacheParam, Tas
             edges.add(edge);
             tableNode.setId(sourceId);
             cacheNode.setId(targetId);
-            cacheNode.setFields((List<String>) attrs.get("fields"));
+            cacheNode.setFields((List<String>) attrs.get(FIELDS));
             cacheNode.setCacheName(saveShareCacheParam.getName());
             cacheNode.setName(cacheNode.getCacheName());
 
@@ -2058,15 +2034,15 @@ private TaskDto parseCacheToTaskDto(SaveShareCacheParam saveShareCacheParam, Tas
     public Map<String, Object> chart(UserDetail user) {
         Map<String, Object> resultChart = new HashMap<>();
         Criteria criteria = new Criteria()
-                .and("is_deleted").ne(true)
-                .and("syncType").in(TaskDto.SYNC_TYPE_MIGRATE, TaskDto.SYNC_TYPE_SYNC)
-                .and("status").nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED)
+                .and(IS_DELETED).ne(true)
+                .and(SYNC_TYPE).in(TaskDto.SYNC_TYPE_MIGRATE, TaskDto.SYNC_TYPE_SYNC)
+                .and(STATUS).nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED)
                 //共享缓存的任务设计的有点问题
                 .and("shareCache").ne(true);
 
 
         Query query = Query.query(criteria);
-        query.fields().include("syncType", "status", "statuses");
+        query.fields().include(SYNC_TYPE, STATUS, "statuses");
         //把任务都查询出来
         List<TaskDto> taskDtoList = DataPermissionMenuEnums.MigrateTack.checkAndSetFilter(
                 user, DataPermissionActionEnums.View, () -> findAllDto(query, user)
@@ -2091,11 +2067,11 @@ public Map<String, Object> chart(UserDetail user) {
 
 
     public Map<String, Integer> inspectChart(UserDetail user) {
-        Criteria criteria = Criteria.where("syncType").is(TaskDto.SYNC_TYPE_MIGRATE)
+        Criteria criteria = Criteria.where(SYNC_TYPE).is(TaskDto.SYNC_TYPE_MIGRATE)
                 .and("isAutoInspect").is(true)
-                .and("is_deleted").ne(true);
+                .and(IS_DELETED).ne(true);
         Query query = new Query(criteria);
-        query.fields().include("_id", "status", "isAutoInspect", "canOpenInspect", "attrs.autoInspectProgress.tableCounts", "attrs.autoInspectProgress.tableIgnore", "attrs.autoInspectProgress.step");
+        query.fields().include("_id", STATUS, "isAutoInspect", "canOpenInspect", "attrs.autoInspectProgress.tableCounts", "attrs.autoInspectProgress.tableIgnore", "attrs.autoInspectProgress.step");
 
         int openTaskNum = 0;
         int canTaskNum = 0;
@@ -2123,7 +2099,7 @@ public Map<String, Integer> inspectChart(UserDetail user) {
         }
 
         Map<String, Integer> chart5 = new HashMap<>();
-        chart5.put("total", openTaskNum);
+        chart5.put(TOTAL, openTaskNum);
         chart5.put("error", errorTaskNum);
         chart5.put("can", canTaskNum);
         chart5.put("diff", diffTaskNum);
@@ -2145,7 +2121,7 @@ public Map<String, Integer> inspectChart(UserDetail user) {
      *
      * @return
      */
-    private Map<String, Object> getDataCopyChart(List<TaskDto> migrateList) {
+    protected Map<String, Object> getDataCopyChart(List<TaskDto> migrateList) {
         Map<String, Object> dataCopyPreview = new HashMap();
 
 
@@ -2175,10 +2151,10 @@ private Map<String, Object> getDataCopyChart(List<TaskDto> migrateList) {
 
             Map<String, Object> singleMap = new HashMap();
             singleMap.put("_id", taskStatus);
-            singleMap.put("count", statusToDataCopyTaskMap.getOrDefault(taskStatus, Collections.emptyList()).size());
+            singleMap.put(COUNT, statusToDataCopyTaskMap.getOrDefault(taskStatus, Collections.emptyList()).size());
             dataCopyPreviewItems.add(singleMap);
         }
-        dataCopyPreview.put("total", migrateList.size());
+        dataCopyPreview.put(TOTAL, migrateList.size());
         dataCopyPreview.put("items", dataCopyPreviewItems);
         return dataCopyPreview;
     }
@@ -2188,7 +2164,7 @@ private Map<String, Object> getDataCopyChart(List<TaskDto> migrateList) {
      *
      * @return
      */
-    private Map<String, Object> getDataDevChart(List<TaskDto> synList) {
+    protected Map<String, Object> getDataDevChart(List<TaskDto> synList) {
         Map<String, Object> dataCopyPreview = new HashMap();
 
         Map<String, Long> statusToCount = new HashMap<>();
@@ -2204,10 +2180,10 @@ private Map<String, Object> getDataDevChart(List<TaskDto> synList) {
         for (String taskStatus : allStatus) {
             Map<String, Object> singleMap = new HashMap();
             singleMap.put("_id", taskStatus);
-            singleMap.put("count", statusToCount.getOrDefault(taskStatus, 0L));
+            singleMap.put(COUNT, statusToCount.getOrDefault(taskStatus, 0L));
             dataCopyPreviewItems.add(singleMap);
         }
-        dataCopyPreview.put("total", synList.size());
+        dataCopyPreview.put(TOTAL, synList.size());
         dataCopyPreview.put("items", dataCopyPreviewItems);
         return dataCopyPreview;
     }
@@ -2250,9 +2226,9 @@ public TaskDetailVo findTaskDetailById(String id, Field field, UserDetail userDe
             String taskId = taskDto.getId().toString();
 
             String type = taskDto.getType();
-            if ("initial_sync".equals(type)) {
+            if (INITIAL_SYNC.equals(type)) {
                 //设置全量开始时间
-                Date initStartTime = getMillstoneTime(taskDto, "READ_SNAPSHOT", "initial_sync");
+                Date initStartTime = getMillstoneTime(taskDto, "READ_SNAPSHOT", INITIAL_SYNC);
                 taskDetailVo.setInitStartTime(initStartTime);
             } else if ("cdc".equals(type)) {
                 //增量开始时间
@@ -2266,9 +2242,9 @@ public TaskDetailVo findTaskDetailById(String id, Field field, UserDetail userDe
 //                //增量最大滞后时间
 //                taskDetailVo.setCdcDelayTime(getCdcDelayTime(taskId));
 
-            } else if ("initial_sync+cdc".equals(type)) {
+            } else if (INITIAL_SYNC_CDC.equals(type)) {
                 //全量开始时间
-                Date initStartTime = getMillstoneTime(taskDto, "READ_SNAPSHOT", "initial_sync");
+                Date initStartTime = getMillstoneTime(taskDto, "READ_SNAPSHOT", INITIAL_SYNC);
                 taskDetailVo.setInitStartTime(initStartTime);
 
                 //增量开始时间
@@ -2299,7 +2275,7 @@ public TaskDetailVo findTaskDetailById(String id, Field field, UserDetail userDe
      * @param taskId
      * @return
      */
-    private Long getLastHour(String taskId) {
+    protected Long getLastHour(String taskId) {
         Long taskLastHour = null;
         try {
             FullSyncVO fullSyncVO = snapshotEdgeProgressService.syncOverview(taskId);
@@ -2321,7 +2297,7 @@ private Long getLastHour(String taskId) {
      * @param TaskDto
      * @return
      */
-    private Date getMillstoneTime(TaskDto TaskDto, String code, String group) {
+    protected Date getMillstoneTime(TaskDto TaskDto, String code, String group) {
         Date millstoneTime = null;
         Optional<Milestone> optionalMilestone = Optional.empty();
         List<Milestone> milestones = TaskDto.getMilestones();
@@ -2335,43 +2311,8 @@ private Date getMillstoneTime(TaskDto TaskDto, String code, String group) {
         return millstoneTime;
     }
 
-//    /**
-//     * 增量延迟时间
-//     *
-//     * @return
-//     */
-//    private Long getCdcDelayTime(String taskId) {
-//        Long cdcDelayTime = null;
-//        MeasurementEntity measurementEntity = measurementService.findByTaskId(taskId);
-//        if (null != measurementEntity && null != measurementEntity.getStatistics() && null != measurementEntity.getStatistics().get("replicateLag")) {
-//            Number cdcDelayTimeNumber = (Number) measurementEntity.getStatistics().get("replicateLag");
-//            cdcDelayTime = cdcDelayTimeNumber.longValue();
-//        }
-//        return cdcDelayTime;
-//    }
-
-
-//    /**
-//     * 获取增量所处时间点
-//     *
-//     * @return
-//     */
-//    private Date getEventTime(String taskId) {
-//        Date eventTime = null;
-//        MeasurementEntity measurementEntity = measurementService.findByTaskId(taskId);
-//        if (null != measurementEntity) {
-//            Number cdcTimestamp = measurementEntity.getStatistics().getOrDefault("cdcTime", 0L);
-//            Long cdcMillSeconds = cdcTimestamp.longValue();
-//            if (cdcMillSeconds > 0) {
-//                eventTime = new Date(cdcMillSeconds);
-//            }
-//        }
-//        return eventTime;
-//    }
-
-
     public Boolean checkRun(String taskId, UserDetail user) {
-        TaskDto taskDto = checkExistById(MongoUtils.toObjectId(taskId), user, "status");
+        TaskDto taskDto = checkExistById(MongoUtils.toObjectId(taskId), user, STATUS);
         return TaskDto.STATUS_EDIT.equals(taskDto.getStatus()) || TaskDto.STATUS_WAIT_START.equals(taskDto.getStatus());
     }
 
@@ -2427,15 +2368,15 @@ public TaskStatsDto stats(UserDetail userDetail) {
         return taskStatsDto;
     }
 
-    private Map<String, Long> typeTaskStats(UserDetail userDetail) {
+    protected Map<String, Long> typeTaskStats(UserDetail userDetail) {
         org.springframework.data.mongodb.core.aggregation.Aggregation aggregation =
                 org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation(
-                        match(Criteria.where("user_id").is(userDetail.getUserId())
+                        match(Criteria.where(USER_ID).is(userDetail.getUserId())
                                 .and("customId").is(userDetail.getCustomerId())
-                                .and("is_deleted").ne(true)
-                                .and("status").nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED)
-                                .and("syncType").in(TaskDto.SYNC_TYPE_MIGRATE,TaskDto.SYNC_TYPE_SYNC)),
-                        group("type").count().as("count")
+                                .and(IS_DELETED).ne(true)
+                                .and(STATUS).nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED)
+                                .and(SYNC_TYPE).in(TaskDto.SYNC_TYPE_MIGRATE,TaskDto.SYNC_TYPE_SYNC)),
+                        group("type").count().as(COUNT)
                 );
 
         Map<String, Long> taskTypeStats = new HashMap<>();
@@ -2454,11 +2395,11 @@ private Map<String, Long> typeTaskStats(UserDetail userDetail) {
             taskTypeStats.put(ParentTaskDto.TYPE_INITIAL_SYNC_CDC, 0L);
         }
         Long total = taskTypeStats.values().stream().reduce(Long::sum).orElse(0L);
-        taskTypeStats.put("total", total);
+        taskTypeStats.put(TOTAL, total);
         return taskTypeStats;
     }
 
-    private DataFlowInsightStatisticsDto mergerStatistics(List<LocalDate> localDates, DataFlowInsightStatisticsDto oldStatistics, DataFlowInsightStatisticsDto newStatistics) {
+    protected DataFlowInsightStatisticsDto mergerStatistics(List<LocalDate> localDates, DataFlowInsightStatisticsDto oldStatistics, DataFlowInsightStatisticsDto newStatistics) {
         Map<String, DataFlowInsightStatisticsDto.DataStatisticInfo> oldMap = new HashMap<>();
         if (oldStatistics != null && CollectionUtils.isNotEmpty(oldStatistics.getInputDataStatistics())) {
             oldMap = oldStatistics.getInputDataStatistics().stream().collect(Collectors.toMap(DataFlowInsightStatisticsDto.DataStatisticInfo::getTime, v -> v, (k1, k2) -> k2));
@@ -2471,7 +2412,7 @@ private DataFlowInsightStatisticsDto mergerStatistics(List<LocalDate> localDates
 
         List<DataFlowInsightStatisticsDto.DataStatisticInfo> inputDataStatistics = new ArrayList<>();
 
-        final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMdd");
+        final DateTimeFormatter format = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
         for (LocalDate localDate : localDates) {
             String time = localDate.format(format);
             if (newMap.get(time) != null) {
@@ -2490,12 +2431,12 @@ private DataFlowInsightStatisticsDto mergerStatistics(List<LocalDate> localDates
         return dataFlowInsightStatisticsDto;
     }
 
-    private List<LocalDate> getNewLocalDate(List<LocalDate> localDates, DataFlowInsightStatisticsDto oldStatistics) {
+    protected List<LocalDate> getNewLocalDate(List<LocalDate> localDates, DataFlowInsightStatisticsDto oldStatistics) {
         List<DataFlowInsightStatisticsDto.DataStatisticInfo> inputDataStatistics = oldStatistics.getInputDataStatistics();
         if (CollectionUtils.isEmpty(inputDataStatistics)) {
             return localDates;
         }
-        final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMdd");
+        final DateTimeFormatter format = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
         List<LocalDate> newLocalDates = new ArrayList<>();
         inputDataStatistics.remove(inputDataStatistics.size()-1);
         List<LocalDate> oldLocalDate = inputDataStatistics.stream().map(s -> LocalDate.parse(s.getTime(), format)).collect(Collectors.toList());
@@ -2534,7 +2475,7 @@ public DataFlowInsightStatisticsDto statsTransport(UserDetail userDetail) {
 
     public DataFlowInsightStatisticsDto statsTransport(UserDetail userDetail, List<LocalDate> localDates) {
 
-        Criteria criteria = Criteria.where("is_deleted").ne(true);
+        Criteria criteria = Criteria.where(IS_DELETED).ne(true);
         Query query = new Query(criteria);
         query.fields().include("_id");
         List<TaskDto> allDto = findAllDto(query, userDetail);
@@ -2560,10 +2501,10 @@ public DataFlowInsightStatisticsDto statsTransport(UserDetail userDetail, List<L
                 .and("tags.type").is("task");
         Query query1 = new Query(in);
         query1.fields().include("ss", "tags");
-        DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMdd");
+        DateTimeFormatter format = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
         List<MeasurementEntity> measurementEntities = measurementServiceV2.find(query1);
 
-        Map<String, List<MeasurementEntity>> taskMap = measurementEntities.stream().collect(Collectors.groupingBy(m -> m.getTags().get("taskId")));
+        Map<String, List<MeasurementEntity>> taskMap = measurementEntities.stream().collect(Collectors.groupingBy(m -> m.getTags().get(TASK_ID)));
 
 
 
@@ -2633,12 +2574,12 @@ public Map<String, List<TaskDto>> getByConIdOfTargetNode(List<String> connection
 
         Map<String, List<TaskDto>> taskMap = new HashMap<>();
         for (String connectionId : connectionIds) {
-            Criteria criteria = Criteria.where("dag.nodes.connectionId").is(connectionId)
-                    .and("is_deleted").ne(true);
+            Criteria criteria = Criteria.where(DAG_NODES_CONNECTION_ID).is(connectionId)
+                    .and(IS_DELETED).ne(true);
             if (StringUtils.isNotBlank(status)) {
-                criteria.and("status").is(status);
+                criteria.and(STATUS).is(status);
             } else {
-                criteria.and("status").nin(Lists.of(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED));
+                criteria.and(STATUS).nin(Lists.of(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED));
             }
             Query query = new Query(criteria);
             query.fields().include("dag");
@@ -2649,7 +2590,7 @@ public Map<String, List<TaskDto>> getByConIdOfTargetNode(List<String> connection
             be:
             for (TaskDto task : allTasks) {
                 DAG dag = task.getDag();
-                if (position.equals("source")) {
+                if (position.equals(SOURCE)) {
                     List<Node> sources = dag.getSources();
                     if (CollectionUtils.isNotEmpty(sources)) {
                         for (Node source : sources) {
@@ -2680,7 +2621,7 @@ public Map<String, List<TaskDto>> getByConIdOfTargetNode(List<String> connection
 
             Criteria criteria1 = Criteria.where("_id").in(containsTaskIds);
             Query query1 = new Query(criteria1);
-            query1.with(Sort.by("createTime").descending());
+            query1.with(Sort.by(CREATE_TIME).descending());
             List<TaskDto> taskDtos = findAllDto(new Query(criteria1), user);
             taskMap.put(connectionId, taskDtos);
         }
@@ -2690,13 +2631,13 @@ public Map<String, List<TaskDto>> getByConIdOfTargetNode(List<String> connection
     }
 
     public long countTaskNumber(UserDetail user) {
-        return count(new Query(Criteria.where("is_deleted").is(false).and("status").nin(TaskDto.STATUS_DELETE_FAILED, TaskDto.STATUS_DELETING)), user);
+        return count(new Query(Criteria.where(IS_DELETED).is(false).and(STATUS).nin(TaskDto.STATUS_DELETE_FAILED, TaskDto.STATUS_DELETING)), user);
     }
 
     public List<SampleTaskVo> findByConId(String sourceConnectionId, String targetConnectionId, String syncType, String status, Where where, UserDetail user) {
 
         Criteria criteria = repository.whereToCriteria(where);
-        criteria.and("is_deleted").ne(true);
+        criteria.and(IS_DELETED).ne(true);
         List<String> conIds = new ArrayList<>();
         if (StringUtils.isNotBlank(sourceConnectionId)) {
             conIds.add(sourceConnectionId);
@@ -2707,19 +2648,19 @@ public List<SampleTaskVo> findByConId(String sourceConnectionId, String targetCo
         }
 
         if (CollectionUtils.isNotEmpty(conIds)) {
-            criteria.and("dag.nodes.connectionId").in(conIds);
+            criteria.and(DAG_NODES_CONNECTION_ID).in(conIds);
         }
 
         if (StringUtils.isNotBlank(syncType)) {
-            criteria.and("syncType").is(syncType);
+            criteria.and(SYNC_TYPE).is(syncType);
         } else {
-            criteria.and("syncType").in(TaskDto.SYNC_TYPE_MIGRATE, TaskDto.SYNC_TYPE_SYNC);
+            criteria.and(SYNC_TYPE).in(TaskDto.SYNC_TYPE_MIGRATE, TaskDto.SYNC_TYPE_SYNC);
         }
 
         if (StringUtils.isNotBlank(status)) {
-            criteria.and("status").is(status);
+            criteria.and(STATUS).is(status);
         } else {
-            criteria.and("status").nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED);
+            criteria.and(STATUS).nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED);
         }
 
         Query query = new Query(criteria);
@@ -2825,20 +2766,6 @@ public static class Char1Group {
         private long count;
     }
 
-    @Data
-    public static class Char2Group {
-        private long totalInput = 0;
-        private long totalOutput = 0;
-        private long totalInputDataSize = 0;
-        private long totalOutputDataSize = 0;
-        private long totalInsert = 0;
-        private long totalInsertSize = 0;
-        private long totalUpdate = 0;
-        private long totalUpdateSize = 0;
-        private long totalDelete = 0;
-        private long totalDeleteSize = 0;
-    }
-
 
     public void batchLoadTask(HttpServletResponse response, List<String> taskIds, UserDetail user) {
         List<TaskUpAndLoadDto> jsonList = new ArrayList<>();
@@ -2873,7 +2800,7 @@ public void batchLoadTask(HttpServletResponse response, List<String> taskIds, Us
                                     metadataInstancesDto.setCustomId(null);
                                     metadataInstancesDto.setLastUpdBy(null);
                                     metadataInstancesDto.setUserId(null);
-                                    jsonList.add(new TaskUpAndLoadDto("MetadataInstances", JsonUtil.toJsonUseJackson(metadataInstancesDto)));
+                                    jsonList.add(new TaskUpAndLoadDto(METADATA_INSTANCES, JsonUtil.toJsonUseJackson(metadataInstancesDto)));
                                 }
                             }
 
@@ -2889,8 +2816,8 @@ public void batchLoadTask(HttpServletResponse response, List<String> taskIds, Us
                                 dataSourceConnectionDto.setDefinitionPdkAPIVersion(byPdkHash.getPdkAPIVersion());
                                 String databaseQualifiedName = MetaDataBuilderUtils.generateQualifiedName("database", dataSourceConnectionDto, null);
                                 MetadataInstancesDto dataSourceMetadataInstance = metadataInstancesService.findOne(
-                                        Query.query(Criteria.where("qualified_name").is(databaseQualifiedName).and("is_deleted").ne(true)), user);
-                                jsonList.add(new TaskUpAndLoadDto("MetadataInstances", JsonUtil.toJsonUseJackson(dataSourceMetadataInstance)));
+                                        Query.query(Criteria.where(QUALIFIED_NAME).is(databaseQualifiedName).and(IS_DELETED).ne(true)), user);
+                                jsonList.add(new TaskUpAndLoadDto(METADATA_INSTANCES, JsonUtil.toJsonUseJackson(dataSourceMetadataInstance)));
                                 jsonList.add(new TaskUpAndLoadDto("Connections", JsonUtil.toJsonUseJackson(dataSourceConnectionDto)));
                             }
 
@@ -2956,7 +2883,7 @@ public void batchUpTask(MultipartFile multipartFile, UserDetail user, boolean co
 
         if (!Objects.requireNonNull(multipartFile.getOriginalFilename()).endsWith("json.gz")) {
             //不支持其他的格式文件
-            throw new BizException("Task.ImportFormatError");
+            throw new BizException(TASK_IMPORT_FORMAT_ERROR);
         }
 
         try {
@@ -2969,12 +2896,12 @@ public void batchUpTask(MultipartFile multipartFile, UserDetail user, boolean co
         } catch (Exception e) {
             //e.printStackTrace();
             //不支持其他的格式文件
-            throw new BizException("Task.ImportFormatError");
+            throw new BizException(TASK_IMPORT_FORMAT_ERROR);
         }
 
         if (taskUpAndLoadDtos == null) {
             //不支持其他的格式文件
-            throw new BizException("Task.ImportFormatError");
+            throw new BizException(TASK_IMPORT_FORMAT_ERROR);
         }
 
         List<MetadataInstancesDto> metadataInstancess = new ArrayList<>();
@@ -2988,7 +2915,7 @@ public void batchUpTask(MultipartFile multipartFile, UserDetail user, boolean co
                 if (StringUtils.isBlank(taskUpAndLoadDto.getJson())) {
                     continue;
                 }
-                if ("MetadataInstances".equals(taskUpAndLoadDto.getCollectionName())) {
+                if (METADATA_INSTANCES.equals(taskUpAndLoadDto.getCollectionName())) {
                     metadataInstancess.add(JsonUtil.parseJsonUseJackson(dtoJson, MetadataInstancesDto.class));
                 } else if ("Task".equals(taskUpAndLoadDto.getCollectionName())) {
                     tasks.add(JsonUtil.parseJsonUseJackson(dtoJson, TaskDto.class));
@@ -3052,8 +2979,8 @@ public void batchImport(List<TaskDto> taskDtos, UserDetail user, boolean cover,
         }
 
         for (TaskDto taskDto : taskDtos) {
-            Query query = new Query(Criteria.where("_id").is(taskDto.getId()).and("is_deleted").ne(true));
-            query.fields().include("_id", "user_id");
+            Query query = new Query(Criteria.where("_id").is(taskDto.getId()).and(IS_DELETED).ne(true));
+            query.fields().include("_id", USER_ID);
             TaskDto one = findOne(query, user);
 
             taskDto.setListtags(null);
@@ -3063,12 +2990,12 @@ public void batchImport(List<TaskDto> taskDtos, UserDetail user, boolean cover,
 
             Map<String, Object> attrs = taskDto.getAttrs();
             if (attrs != null) {
-                attrs.remove("edgeMilestones");
-                attrs.remove("syncProgress");
+                attrs.remove(EDGE_MILESTONES);
+                attrs.remove(SYNC_PROGRESS);
             }
 
             if (one == null) {
-                TaskDto one1 = findOne(new Query(Criteria.where("_id").is(taskDto.getId()).and("is_deleted").ne(true)));
+                TaskDto one1 = findOne(new Query(Criteria.where("_id").is(taskDto.getId()).and(IS_DELETED).ne(true)));
                 if (one1 != null) {
                     taskDto.setId(null);
                     taskDto.getDag().getNodes().forEach(node -> {
@@ -3113,7 +3040,7 @@ public void batchImport(List<TaskDto> taskDtos, UserDetail user, boolean cover,
                         continue;
                     }
                 }
-                repository.getMongoOperations().updateFirst(new Query(Criteria.where("_id").is(taskDto.getId())), Update.update("status", TaskDto.STATUS_EDIT), TaskEntity.class);
+                repository.getMongoOperations().updateFirst(new Query(Criteria.where("_id").is(taskDto.getId())), Update.update(STATUS, TaskDto.STATUS_EDIT), TaskEntity.class);
                 confirmById(taskDto, user, true, true);
             }
         }
@@ -3133,8 +3060,8 @@ public Criteria parseOrToCriteria(Where where) {
             List<Map<String, Map<String, Object>>> orList = (List) where.remove("or");
             for (Map<String, Map<String, Object>> orMap : orList) {
                 orMap.forEach((key, value) -> {
-                    if (value.containsKey("$regex")) {
-                        Object queryStr = value.get("$regex");
+                    if (value.containsKey(REGEX)) {
+                        Object queryStr = value.get(REGEX);
                         Criteria orCriteria = Criteria.where(key).regex(queryStr.toString());
                         criteriaList.add(orCriteria);
                     } else if (value.containsKey("$eq")) {
@@ -3215,13 +3142,13 @@ public void getTableDDL (TaskDto taskDto) {
             String connectionId = source.getConnectionId();
 
             Query instanceQuery = new Query();
-            instanceQuery.addCriteria(Criteria.where("source._id").is(connectionId).and("meta_type").is("table").and("is_deleted").ne("true"));
+            instanceQuery.addCriteria(Criteria.where("source._id").is(connectionId).and("meta_type").is(TABLE).and(IS_DELETED).ne("true"));
             metadataInstancesList = metadataInstancesService.findAll(instanceQuery);
 
         } else {
             List<String> qualifiedNameList = transformerItemList.stream().map(MetadataTransformerItemDto::getSinkQulifiedName).distinct().collect(Collectors.toList());
             Query instantiatedQuery = new Query();
-            instantiatedQuery.addCriteria(Criteria.where("qualified_name").in(qualifiedNameList));
+            instantiatedQuery.addCriteria(Criteria.where(QUALIFIED_NAME).in(qualifiedNameList));
 
             metadataInstancesList = metadataInstancesService.findAll(instantiatedQuery);
         }
@@ -3248,16 +3175,6 @@ public List<TaskDto> findAllTasksByIds(List<String> list) {
 //        return findAll(query);
     }
 
-//    public void updateStatus(ObjectId taskId, String status) {
-//        Query query = Query.query(Criteria.where("_id").is(taskId));
-//        Update update = Update.update("status", status).set("last_updated", new Date());
-//        update(query, update);
-//    }
-
-
-
-
-
     /**
      * 重置子任务之后，情况指标观察数据
      * @param taskId
@@ -3275,18 +3192,20 @@ public UpdateResult renewNotSendMq(TaskDto taskDto, UserDetail user) {
         Update set = resetUpdate();
         set.unset("temp")
                 .unset("milestones")
-                .unset("tmCurrentTime")
+                .unset(TM_CURRENT_TIME)
                 .set("agentTags", null)
                 .set("syncStatus", SyncStatus.NORMAL)
                 .set("scheduleTimes", null)
                 .set("scheduleTime", null)
                 .set("messages", null)
-                .set("errorEvents", null);
+                .set("errorEvents", null)
+                .unset(SHARE_CDC_STOP)
+                .unset(SHARE_CDC_STOP_MESSAGE);
 
 
         if (taskDto.getAttrs() != null) {
-            taskDto.getAttrs().remove("syncProgress");
-            taskDto.getAttrs().remove("edgeMilestones");
+            taskDto.getAttrs().remove(SYNC_PROGRESS);
+            taskDto.getAttrs().remove(EDGE_MILESTONES);
             taskDto.getAttrs().remove("milestone");
             taskDto.getAttrs().remove("nodeMilestones");
             taskDto.getAttrs().remove(TaskDto.ATTRS_USED_SHARE_CACHE);
@@ -3294,7 +3213,7 @@ public UpdateResult renewNotSendMq(TaskDto taskDto, UserDetail user) {
             taskDto.getAttrs().remove("SNAPSHOT_ORDER_LIST");
             AutoInspectUtil.removeProgress(taskDto.getAttrs());
 
-            set.set("attrs", taskDto.getAttrs());
+            set.set(ATTRS, taskDto.getAttrs());
         }
 
 
@@ -3303,14 +3222,14 @@ public UpdateResult renewNotSendMq(TaskDto taskDto, UserDetail user) {
 
         beforeSave(taskDto, user);
         set.unset("tempDag").set("isEdit", true);
-        Criteria criteriaTask = Criteria.where("_id").is(taskDto.getId()).and("status").is(TaskDto.STATUS_RENEWING);
+        Criteria criteriaTask = Criteria.where("_id").is(taskDto.getId()).and(STATUS).is(TaskDto.STATUS_RENEWING);
         UpdateResult updateResult = update(new Query(criteriaTask), set, user);
 
         if (updateResult.getMatchedCount() > 0) {
             if (nodes != null) {
 
                 List<String> nodeIds = nodes.stream().map(Node::getId).collect(Collectors.toList());
-                Criteria criteria = Criteria.where("taskId").is(taskDto.getId().toHexString())
+                Criteria criteria = Criteria.where(TASK_ID).is(taskDto.getId().toHexString())
                         .and("type").is(TaskSnapshotProgress.ProgressType.EDGE_PROGRESS.name())
                         .orOperator(Criteria.where("srcNodeId").in(nodeIds),
                                 Criteria.where("tgtNodeId").in(nodeIds));
@@ -3318,7 +3237,7 @@ public UpdateResult renewNotSendMq(TaskDto taskDto, UserDetail user) {
 
                 snapshotEdgeProgressService.deleteAll(query);
 
-                Criteria criteria1 = Criteria.where("taskId").is(taskDto.getId().toHexString())
+                Criteria criteria1 = Criteria.where(TASK_ID).is(taskDto.getId().toHexString())
                         .and("type").is(TaskSnapshotProgress.ProgressType.TASK_PROGRESS.name());
                 Query query1 = new Query(criteria1);
 
@@ -3328,7 +3247,7 @@ public UpdateResult renewNotSendMq(TaskDto taskDto, UserDetail user) {
         return updateResult;
     }
 
-    private void sendRenewMq(TaskDto taskDto, UserDetail user, String opType) {
+    protected void sendRenewMq(TaskDto taskDto, UserDetail user, String opType) {
         DataSyncMq mq = new DataSyncMq();
         mq.setTaskId(taskDto.getId().toHexString());
         mq.setOpType(opType);
@@ -3349,16 +3268,16 @@ private void sendRenewMq(TaskDto taskDto, UserDetail user, String opType) {
     @NotNull
     private static Update resetUpdate() {
         Update update = new Update()
-                .unset("startTime")
+                .unset(START_TIME)
                 .unset("stopTime")
-                .unset("stopRetryTimes")
-                .unset("currentEventTimestamp")
+                .unset(STOP_RETRY_TIMES)
+                .unset(CURRENT_EVENT_TIMESTAMP)
                 .unset("snapshotDoneAt")
-                .unset("scheduleDate")
-                .unset("stopedDate")
+                .unset(SCHEDULE_DATE)
+                .unset(STOPED_DATE)
                 .unset("functionRetryEx")
                 .unset("taskRetryStatus")
-                .unset("functionRetryStatus");
+                .unset(FUNCTION_RETRY_STATUS);
         return update;
     }
 
@@ -3430,52 +3349,12 @@ private String judgePostgreClearSlot(TaskDto taskDto, String opType) {
 
         if ("PostgreSQL".equalsIgnoreCase(databaseType) &&
                 DataSyncMq.OP_TYPE_DELETE.equals(opType) && MapUtils.isNotEmpty(taskDto.getAttrs())) {
-            return (String) attrs.get("connectionName");
-        }
-        return null;
-    }
-
-//    public boolean deleteById(TaskDto taskDto, UserDetail user) {
-//        //如果子任务在运行中，将任务停止，再删除（在这之前，应该提示用户这个风险）
-//        if (taskDto == null) {
-//            return true;
-//        }
-//
-//        sendRenewMq(taskDto, user, DataSyncMq.OP_TYPE_DELETE);
-//
-//        renewNotSendMq(taskDto, user);
-//
-//        if (runningStatus.contains(taskDto.getStatus())) {
-//            log.warn("task is run, can not delete it");
-//            throw new BizException("Task.DeleteTaskIsRun");
-//        }
-//
-//        //TODO 删除当前模块的模型推演
-//        resetFlag(taskDto.getId(), user, "deleteFlag");
-//        return super.deleteById(taskDto.getId(), user);
-//    }
-
-
-
-    private boolean compareDag(DAG dag, DAG old) {
-        List<Node> nodes = dag.getNodes();
-        List<Node> oldNodes = old.getNodes();
-        if (nodes.size() != oldNodes.size()) {
-            return false;
-        }
-
-        Map<String, Node> oldMap = oldNodes.stream().collect(Collectors.toMap(Node::getId, n -> n));
-        for (Node node : nodes) {
-            Node oldNode = oldMap.get(node.getId());
-            if (oldNode == null) {
-                return false;
-            }
-
-            if (!node.equals(oldNode)) {
-                return false;
+            if (null == attrs){
+                attrs = new HashMap<>();
             }
+            return (String) attrs.get(CONNECTION_NAME);
         }
-        return true;
+        return null;
     }
 
 
@@ -3525,7 +3404,7 @@ public void start(TaskDto taskDto, UserDetail user, String startFlag) {
         Update update = Update.update("lastStartDate", System.currentTimeMillis());
         if (StringUtils.isBlank(taskDto.getTaskRecordId())) {
             String taskRecordId = new ObjectId().toHexString();
-            update.set("taskRecordId", taskRecordId);
+            update.set(TASK_RECORD_ID, taskRecordId);
             taskDto.setTaskRecordId(taskRecordId);
 
             TaskEntity taskSnapshot = new TaskEntity();
@@ -3534,7 +3413,7 @@ public void start(TaskDto taskDto, UserDetail user, String startFlag) {
         }
         if (Objects.isNull(taskDto.getStartTime())) {
             DateTime date = DateUtil.date();
-            update.set("startTime", date);
+            update.set(START_TIME, date);
             taskDto.setStartTime(date);
         }
         update(Query.query(Criteria.where("_id").is(taskDto.getId().toHexString())), update);
@@ -3544,7 +3423,7 @@ public void start(TaskDto taskDto, UserDetail user, String startFlag) {
         if (!taskDto.getShareCache()) {
                 Map<String, List<Message>> validateMessage = taskDto.getDag().validate();
                 if (!validateMessage.isEmpty()) {
-                    throw new BizException("Task.ListWarnMessage", validateMessage);
+                    throw new BizException(TASK_LIST_WARN_MESSAGE, validateMessage);
             }
         }
         //日志挖掘
@@ -3617,13 +3496,13 @@ public void run(TaskDto taskDto, UserDetail user) {
                 log.info("concurrent start operations, this operation don‘t effective, task name = {}", taskDto.getName());
                 return;
             }
-            Query query = new Query(Criteria.where("id").is(taskDto.getId()).and("status").is(taskDto.getStatus()));
+            Query query = new Query(Criteria.where("id").is(taskDto.getId()).and(STATUS).is(taskDto.getStatus()));
             //需要将重启标识清除
             Update set = Update.update("isEdit", false)
-                    .set("restartFlag", false)
-                    .unset("functionRetryStatus")
-                    .set("restartFlag", false)
-                    .set("stopRetryTimes", 0);
+                    .set(RESTART_FLAG, false)
+                    .unset(FUNCTION_RETRY_STATUS)
+                    .set(RESTART_FLAG, false)
+                    .set(STOP_RETRY_TIMES, 0);
             update(query, set, user);
             taskScheduleService.scheduling(taskDto, user);
         } finally {
@@ -3718,18 +3597,18 @@ public void pause(TaskDto taskDto, UserDetail user, boolean force, boolean resta
 
         //重启的特殊处理，共享挖掘的比较多
         Field field = new Field();
-        field.put("status", true);
+        field.put(STATUS, true);
         TaskDto statusTask = findById(taskDto.getId(), field);
         taskDto.setStatus(statusTask.getStatus());
         taskDto.setSyncStatus(statusTask.getSyncStatus());
         if ((TaskDto.STATUS_STOP.equals(taskDto.getStatus()) || TaskDto.STATUS_STOPPING.equals(taskDto.getStatus())) && restart) {
-            Update update = Update.update("restartFlag", true).set("restartUserId", user.getUserId());
+            Update update = Update.update(RESTART_FLAG, true).set("restartUserId", user.getUserId());
             Query query = new Query(Criteria.where("_id").is(taskDto.getId()));
             update(query, update, user);
             return;
         }
 
-        Update stopUpdate = Update.update("stopedDate", System.currentTimeMillis());
+        Update stopUpdate = Update.update(STOPED_DATE, System.currentTimeMillis());
         if (CollectionUtils.isNotEmpty(taskDto.getLdpNewTables())) {
             stopUpdate.set("ldpNewTables", taskDto.getLdpNewTables());
         }
@@ -3751,12 +3630,12 @@ public void pause(TaskDto taskDto, UserDetail user, boolean force, boolean resta
         //将状态改为暂停中，给flowengin发送暂停消息，在回调的消息中将任务改为已暂停
         if (restart) {
             Update update = new Update();
-            update.set("restartFlag", true).set("restartUserId", user.getUserId());
+            update.set(RESTART_FLAG, true).set("restartUserId", user.getUserId());
             Query query1 = new Query(Criteria.where("_id").is(taskDto.getId()));
             update(query1, update, user);
         }
 
-        Update update = Update.update("stopRetryTimes", 0);
+        Update update = Update.update(STOP_RETRY_TIMES, 0);
         updateById(taskDto.getId(), update, user);
 
 
@@ -3791,7 +3670,7 @@ public void sendStoppingMsg(String taskId, String agentId, UserDetail user, bool
     public String running(ObjectId id, UserDetail user) {
 
         //判断子任务是否存在
-        TaskDto taskDto = checkExistById(id, user, "_id", "status", "name", "taskRecordId", "startTime", "scheduleDate");
+        TaskDto taskDto = checkExistById(id, user, "_id", STATUS, "name", TASK_RECORD_ID, START_TIME, SCHEDULE_DATE);
         //将子任务状态改成运行中
         if (!TaskDto.STATUS_WAIT_RUN.equals(taskDto.getStatus())) {
             log.info("concurrent runError operations, this operation don‘t effective, task name = {}", taskDto.getName());
@@ -3805,7 +3684,7 @@ public String running(ObjectId id, UserDetail user) {
         });
 
         Query query1 = new Query(Criteria.where("_id").is(taskDto.getId()));
-        Update update = Update.update("scheduleDate", null);
+        Update update = Update.update(SCHEDULE_DATE, null);
 
         StateMachineResult stateMachineResult = stateMachineService.executeAboutTask(taskDto, DataFlowEvent.RUNNING, user);
         if (stateMachineResult.isFail()) {
@@ -3824,7 +3703,7 @@ public String running(ObjectId id, UserDetail user) {
      */
     public String runError(ObjectId id, UserDetail user, String errMsg, String errStack) {
         //判断任务是否存在。
-        TaskDto taskDto = checkExistById(id, user, "_id", "status", "name", "taskRecordId");
+        TaskDto taskDto = checkExistById(id, user, "_id", STATUS, "name", TASK_RECORD_ID);
 
         //将子任务状态更新成错误.
         StateMachineResult stateMachineResult = stateMachineService.executeAboutTask(taskDto, DataFlowEvent.ERROR, user);
@@ -3844,7 +3723,7 @@ public String runError(ObjectId id, UserDetail user, String errMsg, String errSt
      */
     public String complete(ObjectId id, UserDetail user) {
         //判断子任务是否存在
-        TaskDto taskDto = checkExistById(id, user, "_id", "status", "name", "taskRecordId");
+        TaskDto taskDto = checkExistById(id, user, "_id", STATUS, "name", TASK_RECORD_ID);
 
         StateMachineResult stateMachineResult = stateMachineService.executeAboutTask(taskDto, DataFlowEvent.COMPLETED, user);
         if (stateMachineResult.isFail()) {
@@ -3862,7 +3741,7 @@ public String complete(ObjectId id, UserDetail user) {
      */
     public String stopped(ObjectId id, UserDetail user) {
         //判断子任务是否存在。
-        TaskDto taskDto = checkExistById(id, user, "dag", "name", "status", "_id", "taskRecordId", "agentId", "stopedDate", "restartFlag");
+        TaskDto taskDto = checkExistById(id, user, "dag", "name", STATUS, "_id", TASK_RECORD_ID, AGENT_ID, STOPED_DATE, RESTART_FLAG);
 
         StateMachineResult stateMachineResult = stateMachineService.executeAboutTask(taskDto, DataFlowEvent.STOPPED, user);
 
@@ -3876,8 +3755,8 @@ public String stopped(ObjectId id, UserDetail user) {
                 monitoringLogsService.startTaskErrorLog(taskDto, user, msg, Level.INFO);
             });
 
-            Update update = Update.update("stopRetryTimes", 0).unset("stopedDate")
-                    .unset("functionRetryStatus");
+            Update update = Update.update(STOP_RETRY_TIMES, 0).unset(STOPED_DATE)
+                    .unset(FUNCTION_RETRY_STATUS);
             updateById(id, update, user);
 
             logCollectorService.endConnHeartbeat(user, taskDto); // 尝试停止心跳任务
@@ -3922,7 +3801,7 @@ public RunTimeInfo runtimeInfo(ObjectId id, Long endTime, UserDetail user) {
 
     public void updateNode(ObjectId objectId, String nodeId, Document param, UserDetail user) {
         TaskDto TaskDto = checkExistById(objectId, user);
-        Criteria criteria = Criteria.where("_id").is(objectId).and("dag.nodes").elemMatch(Criteria.where("id").is(nodeId));
+        Criteria criteria = Criteria.where("_id").is(objectId).and(DAG_NODES).elemMatch(Criteria.where("id").is(nodeId));
         Document set = (Document) param.get("$set");
         for (String s : set.keySet()) {
             set.put("dag.nodes.$." + s, set.get(s));
@@ -3947,14 +3826,14 @@ public void updateSyncProgress(ObjectId taskId, Document document) {
 
     public void increaseClear(ObjectId taskId, String srcNode, String tgtNode, UserDetail user) {
         //清理只需要清楚syncProgress数据就行
-        TaskDto TaskDto = checkExistById(taskId, user, "attrs");
+        TaskDto TaskDto = checkExistById(taskId, user, ATTRS);
         clear(srcNode, tgtNode, user, TaskDto);
 
     }
 
-    private void clear(String srcNode, String tgtNode, UserDetail user, TaskDto TaskDto) {
+    protected void clear(String srcNode, String tgtNode, UserDetail user, TaskDto TaskDto) {
         Map<String, Object> attrs = TaskDto.getAttrs();
-        Object syncProgress = attrs.get("syncProgress");
+        Object syncProgress = attrs.get(SYNC_PROGRESS);
         if (syncProgress == null) {
             return;
         }
@@ -3964,13 +3843,13 @@ private void clear(String srcNode, String tgtNode, UserDetail user, TaskDto Task
 
         syncProgressMap.remove(JsonUtil.toJsonUseJackson(key));
 
-        Update update = Update.update("attrs", attrs);
+        Update update = Update.update(ATTRS, attrs);
         //不需要刷新主任状态， 所以调用super, 本来中重新的自带刷新主任务状态
         super.updateById(TaskDto.getId(), update, user);
     }
 
     public void increaseBacktracking(ObjectId taskId, String srcNode, String tgtNode, TaskDto.SyncPoint point, UserDetail user) {
-        TaskDto taskDto = checkExistById(taskId, user, "parentId", "attrs", "dag", "syncPoints");
+        TaskDto taskDto = checkExistById(taskId, user, "parentId", ATTRS, "dag", SYNC_POINTS);
         clear(srcNode, tgtNode, user, taskDto);
 
 
@@ -4000,7 +3879,7 @@ public void increaseBacktracking(ObjectId taskId, String srcNode, String tgtNode
             syncPoint.setConnectionId(((DataParentNode<?>) node).getConnectionId());
 
             if (exist) {
-                Criteria criteriaPoint = Criteria.where("_id").is(taskDto.getId()).and("syncPoints")
+                Criteria criteriaPoint = Criteria.where("_id").is(taskDto.getId()).and(SYNC_POINTS)
                         .elemMatch(Criteria.where("nodeId").is(node.getId()));
                 Update update = Update.update("syncPoints.$", syncPoint);
                 //更新内嵌文档
@@ -4008,53 +3887,16 @@ public void increaseBacktracking(ObjectId taskId, String srcNode, String tgtNode
             } else {
                 syncPoints.add(syncPoint);
                 Criteria criteriaPoint = Criteria.where("_id").is(taskDto.getId());
-                Update update = Update.update("syncPoints", syncPoints);
+                Update update = Update.update(SYNC_POINTS, syncPoints);
                 update(new Query(criteriaPoint), update);
             }
         }
 
     }
 
-
-
-//    public void reseted(ObjectId objectId, UserDetail userDetail) {
-//        TaskDto TaskDto = checkExistById(objectId, userDetail, "_id");
-//        if (TaskDto != null) {
-//            super.updateById(objectId, Update.update("resetFlag", true), userDetail);
-//        }
-//    }
-//
-//    public void deleted(ObjectId objectId, UserDetail userDetail) {
-//        TaskDto TaskDto = checkExistById(objectId, userDetail, "_id");
-//        if (TaskDto != null) {
-//            super.updateById(objectId, Update.update("deleteFlag", true), userDetail);
-//        }
-//    }
-
-    
-
-//    public boolean checkDeleteFlag(ObjectId id, UserDetail user) {
-//        TaskDto TaskDto = checkExistById(id, user, "deleteFlag");
-//        if (TaskDto.getDeleteFlag() != null) {
-//            return TaskDto.getDeleteFlag();
-//        }
-//        return false;
-//    }
-//
-//    public boolean checkResetFlag(ObjectId id, UserDetail user) {
-//        TaskDto TaskDto = checkExistById(id, user, "resetFlag");
-//        if (TaskDto.getResetFlag() != null) {
-//            return TaskDto.getResetFlag();
-//        }
-//        return false;
-//    }
-//    public void resetFlag(ObjectId id, UserDetail user, String flag) {
-//        updateById(id, new Update().unset(flag), user);
-//    }
-
     public void startPlanMigrateDagTask() {
-        Criteria migrateCriteria = Criteria.where("status").is(TaskDto.STATUS_WAIT_START)
-                .and("planStartDateFlag").is(true)
+        Criteria migrateCriteria = Criteria.where(STATUS).is(TaskDto.STATUS_WAIT_START)
+                .and(PLAN_START_DATE_FLAG).is(true)
                 .and("crontabScheduleMsg").is(null)
                 .and("planStartDate").lte(DateUtil.current());
         Query taskQuery = new Query(migrateCriteria);
@@ -4077,7 +3919,7 @@ public void startPlanMigrateDagTask() {
 							try {
 								start(taskDto, userDetail, "11");
 								//启动过后，应该更新掉这个自动启动计划
-								Update unset = new Update().unset("planStartDateFlag").unset("planStartDate");
+								Update unset = new Update().unset(PLAN_START_DATE_FLAG).unset("planStartDate");
 								updateById(taskDto.getId(), unset, finalUserMap.get(taskDto.getUserId()));
 							} catch (Exception e) {
 								log.warn("Start plan migrate task Failed: {}", e.getMessage(), e);
@@ -4093,11 +3935,11 @@ public void startPlanMigrateDagTask() {
     }
 
     public void startPlanCronTask() {
-        Criteria migrateCriteria = Criteria.where("crontabExpressionFlag").is(true)
+        Criteria migrateCriteria = Criteria.where(CRONTAB_EXPRESSION_FLAG).is(true)
                 .and("type").in(ParentTaskDto.TYPE_INITIAL_SYNC, ParentTaskDto.TYPE_INITIAL_SYNC_CDC)
                 .and("crontabExpression").exists(true)
-                .and("is_deleted").is(false)
-                .andOperator(Criteria.where("status").nin(TaskDto.STATUS_EDIT,TaskDto.STATUS_STOPPING,
+                .and(IS_DELETED).is(false)
+                .andOperator(Criteria.where(STATUS).nin(TaskDto.STATUS_EDIT,TaskDto.STATUS_STOPPING,
                         TaskDto.STATUS_RENEWING,TaskDto.STATUS_DELETING,TaskDto.STATUS_SCHEDULING,
                         TaskDto.STATUS_DELETE_FAILED));
         Query taskQuery = new Query(migrateCriteria);
@@ -4122,7 +3964,7 @@ public void startPlanCronTask() {
     }
 
     public TaskDto findByCacheName(String cacheName, UserDetail user) {
-        Criteria taskCriteria = Criteria.where("dag.nodes").elemMatch(Criteria.where(CATALOG).is("memCache").and("cacheName").is(cacheName)).and("is_deleted").is(false);
+        Criteria taskCriteria = Criteria.where(DAG_NODES).elemMatch(Criteria.where(CATALOG).is("memCache").and("cacheName").is(cacheName)).and(IS_DELETED).is(false);
         Query query = new Query(taskCriteria);
 
         return findOne(query, user);
@@ -4134,8 +3976,8 @@ public void updateDag(TaskDto taskDto, UserDetail user, boolean saveHistory) {
     }
 
     public TaskDto findByVersionTime(String id, Long time) {
-        Criteria criteria = Criteria.where("taskId").is(id);
-        criteria.and("tmCurrentTime").is(time);
+        Criteria criteria = Criteria.where(TASK_ID).is(id);
+        criteria.and(TM_CURRENT_TIME).is(time);
 
         Query query = new Query(criteria);
 
@@ -4155,8 +3997,8 @@ public TaskDto findByVersionTime(String id, Long time) {
      * @return
      */
     public void clean(String taskId, Long time) {
-        Criteria criteria = Criteria.where("taskId").is(taskId);
-        criteria.and("tmCurrentTime").gt(time);
+        Criteria criteria = Criteria.where(TASK_ID).is(taskId);
+        criteria.and(TM_CURRENT_TIME).gt(time);
 
         Query query = new Query(criteria);
         repository.getMongoOperations().remove(query, "DDlTaskHistories");
@@ -4192,7 +4034,7 @@ public void updateTaskLogSetting(String taskId, LogSettingParam logSettingParam,
         ObjectId taskObjectId = new ObjectId(taskId);
         TaskDto task = findById(taskObjectId);
         if (null == task) {
-            throw new BizException("Task.NotFound", "The task does not exist");
+            throw new BizException(TASK_NOT_FOUND, "The task does not exist");
         }
 
         Map<String, Object> logSetting = task.getLogSetting();
@@ -4213,7 +4055,7 @@ public void updateTaskLogSetting(String taskId, LogSettingParam logSettingParam,
     }
 
     public Chart6Vo chart6(UserDetail user) {
-        Criteria criteria = Criteria.where("is_deleted").ne(true).and("syncType").in(TaskDto.SYNC_TYPE_SYNC, TaskDto.SYNC_TYPE_MIGRATE);
+        Criteria criteria = Criteria.where(IS_DELETED).ne(true).and(SYNC_TYPE).in(TaskDto.SYNC_TYPE_SYNC, TaskDto.SYNC_TYPE_MIGRATE);
         Query query = new Query(criteria);
         query.fields().include("_id");
         List<TaskDto> allDto = findAllDto(query, user);
@@ -4284,7 +4126,7 @@ public Chart6Vo chart6(UserDetail user) {
     }
 
     public void stopTaskIfNeedByAgentId(String agentId, UserDetail userDetail) {
-        Query query = Query.query(Criteria.where("agentId").is(agentId).and("status").is(TaskDto.STATUS_STOPPING));
+        Query query = Query.query(Criteria.where(AGENT_ID).is(agentId).and(STATUS).is(TaskDto.STATUS_STOPPING));
         query.fields().include("_id");
         List<TaskDto> needStopTasks = findAllDto(query, userDetail);
         for (TaskDto needStopTask : needStopTasks) {
@@ -4295,11 +4137,11 @@ public void stopTaskIfNeedByAgentId(String agentId, UserDetail userDetail) {
 
     public List<TaskDto> getTaskStatsByTableNameOrConnectionId(String connectionId, String tableName, UserDetail userDetail) {
         if (StringUtils.isBlank(connectionId)) {
-            throw new BizException("IllegalArgument", "connectionId");
+            throw new BizException(ILLEGAL_ARGUMENT, CONNECTION_ID);
         }
         Criteria criteria = new Criteria();
         // tableName 不为空根据表查询。否则根据连接查询
-        criteria.and("dag.nodes.connectionId").is(connectionId).and("is_deleted").ne(true);
+        criteria.and(DAG_NODES_CONNECTION_ID).is(connectionId).and(IS_DELETED).ne(true);
         if (StringUtils.isNotBlank(tableName)) {
             criteria.orOperator(new Criteria().and("dag.nodes.tableName").is(tableName),
                     new Criteria().and("dag.nodes.syncObjects.objectNames").is(tableName)
@@ -4311,15 +4153,15 @@ public List<TaskDto> getTaskStatsByTableNameOrConnectionId(String connectionId,
 
     public TableStatusInfoDto getTableStatus(String connectionId, String tableName, UserDetail userDetail) {
         if (StringUtils.isBlank(connectionId)) {
-            throw new BizException("IllegalArgument", "connectionId");
+            throw new BizException(ILLEGAL_ARGUMENT, CONNECTION_ID);
         }
         if (StringUtils.isBlank(tableName)) {
-            throw new BizException("IllegalArgument", "tableName");
+            throw new BizException(ILLEGAL_ARGUMENT, TABLE_NAME);
         }
         TableStatusInfoDto tableStatusInfoDto = new TableStatusInfoDto();
         Criteria criteria = new Criteria();
         // tableName 不为空根据表查询。否则根据连接查询
-        criteria.and("dag.nodes.connectionId").is(connectionId).and("is_deleted").ne(true);
+        criteria.and(DAG_NODES_CONNECTION_ID).is(connectionId).and(IS_DELETED).ne(true);
         criteria.orOperator(new Criteria().and("dag.nodes.tableName").is(tableName),
                 new Criteria().and("dag.nodes.tableNames").in(tableName),
                 new Criteria().and("dag.nodes.syncObjects.objectNames").in(tableName));
@@ -4376,7 +4218,7 @@ public boolean judgeTargetInspect(String connectionId, String tableName, UserDet
         criteriaInspect.and("stats.target.connectionId").is(connectionId);
         criteriaInspect.and("stats.target.table").is(tableName);
         Query queryInspect = Query.query(criteriaInspect);
-        queryInspect.with(Sort.by("createTime").descending());
+        queryInspect.with(Sort.by(CREATE_TIME).descending());
         InspectResultDto inspectResultDto = inspectResultService.findOne(queryInspect,userDetail);
         if (inspectResultDto == null) {
             return true;
@@ -4444,9 +4286,9 @@ public boolean judgeTargetNode(TaskDto taskDto, String tableName) {
 
 
     public List<TaskDto> findHeartbeatByConnectionId(String connectionId, String... includeFields) {
-        Query query = Query.query(Criteria.where("dag.nodes.connectionId").is(connectionId)
-                .and("syncType").is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
-                .and("is_deleted").is(false)
+        Query query = Query.query(Criteria.where(DAG_NODES_CONNECTION_ID).is(connectionId)
+                .and(SYNC_TYPE).is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
+                .and(IS_DELETED).is(false)
         );
         if (null != includeFields && includeFields.length > 0) {
             query.fields().include(includeFields);
@@ -4457,8 +4299,8 @@ public List<TaskDto> findHeartbeatByConnectionId(String connectionId, String...
 
     public TaskDto findHeartbeatByTaskId(String taskId, String... includeFields) {
         Query query = Query.query(Criteria.where(ConnHeartbeatUtils.TASK_RELATION_FIELD).is(taskId)
-                .and("syncType").is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
-                .and("is_deleted").is(false)
+                .and(SYNC_TYPE).is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
+                .and(IS_DELETED).is(false)
         );
         if (null != includeFields && includeFields.length > 0) {
             query.fields().include(includeFields);
@@ -4469,7 +4311,7 @@ public TaskDto findHeartbeatByTaskId(String taskId, String... includeFields) {
 
     public int deleteHeartbeatByConnId(UserDetail user, String connId) {
         int deleteSize = 0;
-        List<TaskDto> heartbeatTasks = findHeartbeatByConnectionId(connId, "_id", "status", "is_deleted");
+        List<TaskDto> heartbeatTasks = findHeartbeatByConnectionId(connId, "_id", STATUS, IS_DELETED);
         if (null != heartbeatTasks) {
             TaskDto statusDto;
             for (TaskDto dto : heartbeatTasks) {
@@ -4485,7 +4327,7 @@ public int deleteHeartbeatByConnId(UserDetail user, String connId) {
                     } catch (InterruptedException e) {
                         throw new RuntimeException("Delete heartbeat task failed");
                     }
-                    statusDto = findByTaskId(dto.getId(), "status");
+                    statusDto = findByTaskId(dto.getId(), STATUS);
                 } while (null != statusDto);
 
                 remove(dto.getId(), user);
@@ -4497,12 +4339,12 @@ public int deleteHeartbeatByConnId(UserDetail user, String connId) {
 
 
     public int runningTaskNum(String processId, UserDetail userDetail) {
-        long workNum = count(Query.query(Criteria.where("agentId").is(processId)
-                .and("is_deleted").ne(true).and("syncType").in(TaskDto.SYNC_TYPE_SYNC, TaskDto.SYNC_TYPE_MIGRATE)
-                .and("status").nin(TaskDto.STATUS_DELETE_FAILED,TaskDto.STATUS_DELETING)
-                        .orOperator(Criteria.where("status").in(TaskDto.STATUS_RUNNING, TaskDto.STATUS_SCHEDULING, TaskDto.STATUS_WAIT_RUN),
-                                Criteria.where("planStartDateFlag").is(true),
-                                Criteria.where("crontabExpressionFlag").is(true)
+        long workNum = count(Query.query(Criteria.where(AGENT_ID).is(processId)
+                .and(IS_DELETED).ne(true).and(SYNC_TYPE).in(TaskDto.SYNC_TYPE_SYNC, TaskDto.SYNC_TYPE_MIGRATE)
+                .and(STATUS).nin(TaskDto.STATUS_DELETE_FAILED,TaskDto.STATUS_DELETING)
+                        .orOperator(Criteria.where(STATUS).in(TaskDto.STATUS_RUNNING, TaskDto.STATUS_SCHEDULING, TaskDto.STATUS_WAIT_RUN),
+                                Criteria.where(PLAN_START_DATE_FLAG).is(true),
+                                Criteria.where(CRONTAB_EXPRESSION_FLAG).is(true)
                         )), userDetail);
         return (int) workNum;
     }
@@ -4513,7 +4355,7 @@ public TaskEntity convertToEntity(Class entityClass, BaseDto dto, String... igno
             return null;
         try {
             TaskEntity entity = new TaskEntity();
-            BeanUtils.copyProperties(dto, entity, "agentId", "startTime", "lastStartDate", "shareCdcStop", "shareCdcStopMessage");
+            BeanUtils.copyProperties(dto, entity, AGENT_ID, START_TIME, "lastStartDate", SHARE_CDC_STOP, SHARE_CDC_STOP_MESSAGE);
             return entity;
         } catch (Exception e) {
             log.error("Convert entity " + entityClass + " failed. {}", ThrowableUtils.getStackTraceByPn(e));
@@ -4523,7 +4365,7 @@ public TaskEntity convertToEntity(Class entityClass, BaseDto dto, String... igno
 
     @Override
     public <T extends BaseDto> T convertToDto(TaskEntity entity, Class<T> dtoClass, String... ignoreProperties) {
-        T dto = super.convertToDto(entity, dtoClass, "shareCdcStopMessage");
+        T dto = super.convertToDto(entity, dtoClass, SHARE_CDC_STOP_MESSAGE);
         try {
             if (dto instanceof TaskDto) {
                 TaskDto taskDto = (TaskDto) dto;
@@ -4539,25 +4381,25 @@ public <T extends BaseDto> T convertToDto(TaskEntity entity, Class<T> dtoClass,
     }
     public int findRunningTasksByAgentId(String processId){
         if (StringUtils.isBlank(processId.trim())) throw new IllegalArgumentException("process id can not be empty");
-        Query query = Query.query(Criteria.where("agentId").is(processId).and("status").is("running"));
+        Query query = Query.query(Criteria.where(AGENT_ID).is(processId).and(STATUS).is("running"));
         List<TaskDto> runningTasks = findAll(query);
         return runningTasks.size();
     }
 
     public int runningTaskNum(UserDetail userDetail) {
-        long workNum = count(Query.query(Criteria.where("is_deleted").ne(true)
-                .and("syncType").in(TaskDto.SYNC_TYPE_SYNC, TaskDto.SYNC_TYPE_MIGRATE)
-                .and("status").nin(TaskDto.STATUS_DELETE_FAILED,TaskDto.STATUS_DELETING)
-                .orOperator(Criteria.where("status").in(TaskDto.STATUS_RUNNING, TaskDto.STATUS_SCHEDULING, TaskDto.STATUS_WAIT_RUN),
-                        Criteria.where("planStartDateFlag").is(true),
-                        Criteria.where("crontabExpressionFlag").is(true)
+        long workNum = count(Query.query(Criteria.where(IS_DELETED).ne(true)
+                .and(SYNC_TYPE).in(TaskDto.SYNC_TYPE_SYNC, TaskDto.SYNC_TYPE_MIGRATE)
+                .and(STATUS).nin(TaskDto.STATUS_DELETE_FAILED,TaskDto.STATUS_DELETING)
+                .orOperator(Criteria.where(STATUS).in(TaskDto.STATUS_RUNNING, TaskDto.STATUS_SCHEDULING, TaskDto.STATUS_WAIT_RUN),
+                        Criteria.where(PLAN_START_DATE_FLAG).is(true),
+                        Criteria.where(CRONTAB_EXPRESSION_FLAG).is(true)
                 )), userDetail);
         return (int) workNum;
     }
 
     public boolean checkCloudTaskLimit(ObjectId taskId,UserDetail user,boolean checkCurrentTask){
         if (settingsService.isCloud()) {
-            TaskDto task = findByTaskId(taskId,"id","agentId","agentTags");
+            TaskDto task = findByTaskId(taskId,"id",AGENT_ID,"agentTags");
             CalculationEngineVo calculationEngineVo = workerService.calculationEngine(task, user, null);
             int runningNum;
             if(checkCurrentTask){
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/TransformSchemaService.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/TransformSchemaService.java
index eb292d2cb7..5bcb18921b 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/TransformSchemaService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/TransformSchemaService.java
@@ -542,4 +542,40 @@ public boolean checkTaskContainJs(TaskDto taskDto) {
         }
         return false;
     }
+
+    /**
+     * transform schema before target node do dynamic table name
+     * */
+    public void transformSchemaBeforeDynamicTableName(TaskDto taskDto, UserDetail user) {
+        if (!TaskDto.SYNC_TYPE_SYNC.equals(taskDto.getSyncType())) {
+            return;
+        }
+        DAG dag = taskDto.getDag();
+        List<Node> targets = dag.getTargets();
+        for (Node<?> target : targets) {
+            if (target instanceof TableNode) {
+                TableNode tableNode = (TableNode)target;
+                if (Boolean.TRUE.equals(tableNode.getNeedDynamicTableName())) {
+                    transformSchemaAndUpdateTask(taskDto, user);
+                    return;
+                }
+            }
+        }
+    }
+
+    public void transformSchemaAndUpdateTask(TaskDto taskDto, UserDetail user) {
+        DAG dag = taskDto.getDag();
+        ObjectId taskId = taskDto.getId();
+        transformSchema(dag, user, taskId);
+        //For now, update like this to save the primary keys data generated in the inference of the join node
+        long count = dag.getNodes()
+                .stream()
+                .filter(n -> NodeEnum.join_processor.name().equals(n.getType()))
+                .count();
+        if (count != 0) {
+            Update update = new Update();
+            update.set("dag", dag);
+            taskService.updateById(taskId, update, user);
+        }
+    }
 }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/webhook/controller/WebHookController.java b/manager/tm/src/main/java/com/tapdata/tm/webhook/controller/WebHookController.java
new file mode 100644
index 0000000000..952c459ae7
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/webhook/controller/WebHookController.java
@@ -0,0 +1,129 @@
+package com.tapdata.tm.webhook.controller;
+
+import com.tapdata.tm.base.controller.BaseController;
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.dto.ResponseMessage;
+import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.utils.WebUtils;
+import com.tapdata.tm.webhook.dto.HookOneHistoryDto;
+import com.tapdata.tm.webhook.dto.WebHookInfoDto;
+import com.tapdata.tm.webhook.server.WebHookService;
+import com.tapdata.tm.webhook.vo.WebHookInfoVo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * @author gavin'xiao
+ * @date 2024/5/10
+ */
+@RestController
+@RequestMapping("/api/webhook")
+@Setter(onMethod_ = {@Autowired})
+@Slf4j
+public class WebHookController extends BaseController {
+    WebHookService<WebHookInfoVo> webHookService;
+
+    @Operation(summary = "find all web hook info of current user")
+    @GetMapping("list")
+    public ResponseMessage<Page<WebHookInfoVo>> currentUserWebHookInfoList(@Parameter(in = ParameterIn.QUERY,
+            description = "Filter defining fields, where, sort, skip, and limit - must be a JSON-encoded string (`{\"where\":{\"something\":\"value\"},\"fields\":{\"something\":true|false},\"sort\": [\"name desc\"],\"page\":1,\"size\":20}`)."
+    )
+                                                                           @RequestParam(value = "filter", required = false) String filterJson,
+                                                                           HttpServletRequest request) {
+        Locale locale = WebUtils.getLocale(request);
+        return success(webHookService.list(parseFilter(filterJson), getLoginUser(), locale));
+    }
+
+    @Operation(summary = "create a web hook info")
+    @PostMapping("create")
+    public ResponseMessage<WebHookInfoVo> create(@RequestBody WebHookInfoDto dto, HttpServletRequest request) {
+        Locale locale = WebUtils.getLocale(request);
+        dto.setLocale(locale);
+        return success(webHookService.create(dto, getLoginUser()));
+    }
+
+    @Operation(summary = "find web hook info by hook id")
+    @GetMapping("{id}")
+    public ResponseMessage<WebHookInfoVo> findWebHookByHookId(@PathVariable(value = "id") String hookId) {
+        return success(webHookService.findWebHookByHookId(hookId, getLoginUser()));
+    }
+
+    @Operation(summary = "update web hook info")
+    @PostMapping("update")
+    public ResponseMessage<WebHookInfoVo> update(@RequestBody WebHookInfoDto dto) {
+        return success(webHookService.update(dto, getLoginUser()));
+    }
+
+
+    @Operation(summary = "close one web hook info by hook id")
+    @PostMapping("closeOne/{id}")
+    public ResponseMessage<WebHookInfoVo> closeOneWebHookByHookId(@PathVariable(value = "id") String hookId) {
+        List<WebHookInfoVo> closed = webHookService.close(new String[]{hookId}, getLoginUser());
+        if (closed.isEmpty()) {
+            return success(null);
+        }
+        return success(closed.get(0));
+    }
+
+    @Operation(summary = "close many web hook info by hook ids")
+    @PostMapping("close")
+    public ResponseMessage<List<WebHookInfoVo>> closeWebHookByHookIds(@RequestParam(value = "ids") String[] ids) {
+        return success(webHookService.close(ids, getLoginUser()));
+    }
+
+
+    @Operation(summary = "Re-Open one web hook info by hook id")
+    @PostMapping("re-open-one/{id}")
+    public ResponseMessage<WebHookInfoVo> reOpenOne(@PathVariable(value = "id") String hookId) {
+        List<WebHookInfoVo> closed = webHookService.reOpen(new String[]{hookId}, getLoginUser());
+        if (closed.isEmpty()) {
+            return failed("webhook.reOpen.failed");
+        }
+        return success(closed.get(0));
+    }
+
+    @Operation(summary = "Re-Open many web hook info by hook ids")
+    @PostMapping("re-open")
+    public ResponseMessage<List<WebHookInfoVo>> reOpenAll(@RequestParam(value = "ids") String[] ids) {
+        return success(webHookService.reOpen(ids, getLoginUser()));
+    }
+
+
+    @Operation(summary = "delete one web hook info by hook id")
+    @DeleteMapping("deleteOne/{id}")
+    public ResponseMessage<Void> deleteOneWebHookByHookId(@PathVariable(value = "id") String hookId) {
+        webHookService.delete(new String[]{hookId}, getLoginUser());
+        return success();
+    }
+
+    @Operation(summary = "delete many web hook info by hook ids")
+    @DeleteMapping("delete")
+    public ResponseMessage<Void> deleteWebHookByHookIds(@RequestParam(value = "ids") String[] ids) {
+        webHookService.delete(ids, getLoginUser());
+        return success();
+    }
+
+    @Operation(summary = "ping test")
+    @PostMapping("ping")
+    public ResponseMessage<HookOneHistoryDto> ping(@RequestBody WebHookInfoDto webHookEvent) {
+        ResponseMessage<HookOneHistoryDto> success = success(webHookService.ping(webHookEvent, getLoginUser()));
+        success.setMessage(MessageUtil.getMessage("webhook.ping.succeed"));
+        return success;
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/webhook/controller/WebHookHistoryController.java b/manager/tm/src/main/java/com/tapdata/tm/webhook/controller/WebHookHistoryController.java
new file mode 100644
index 0000000000..a94ff70a48
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/webhook/controller/WebHookHistoryController.java
@@ -0,0 +1,66 @@
+package com.tapdata.tm.webhook.controller;
+
+import com.tapdata.tm.base.controller.BaseController;
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.dto.ResponseMessage;
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.utils.WebUtils;
+import com.tapdata.tm.webhook.dto.HookOneHistoryDto;
+import com.tapdata.tm.webhook.params.HistoryPageParam;
+import com.tapdata.tm.webhook.server.WebHookHistoryService;
+import com.tapdata.tm.webhook.vo.WebHookHistoryInfoVo;
+import io.swagger.v3.oas.annotations.Operation;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Locale;
+
+/**
+ * @author gavin'xiao
+ * @date 2024/5/10
+ */
+@RestController
+@RequestMapping("/api/webhook/history")
+@Setter(onMethod_ = {@Autowired})
+@Slf4j
+public class WebHookHistoryController extends BaseController {
+    WebHookHistoryService<WebHookHistoryInfoVo> webHookHistoryService;
+
+    @Operation(summary = "find all web hook send history of current user")
+    @GetMapping("list")
+    public ResponseMessage<Page<WebHookHistoryInfoVo>> currentUserWebHookInfoList(@RequestParam(value = "hookId") String hookId,
+                                                                                  @RequestParam(value = "pageFrom") int pageFrom,
+                                                                                  @RequestParam(value = "pageSize") int pageSize,
+                                                                                  HttpServletRequest request) {
+        HistoryPageParam pageParam = new HistoryPageParam();
+        if (StringUtils.isBlank(hookId)) {
+            throw new BizException("webhook.history.hook.id.error");
+        }
+        pageParam.setHookId(hookId);
+        if (pageFrom < 0) {
+            throw new BizException("webhook.history.page.from.error", hookId);
+        }
+        pageParam.setPageFrom(pageFrom);
+        if (pageSize < 1) {
+            throw new BizException("webhook.history.page.size.error", pageSize);
+        }
+        pageParam.setPageSize(pageSize);
+        Locale locale = WebUtils.getLocale(request);
+        return success(webHookHistoryService.list(pageParam, getLoginUser(), locale));
+    }
+
+    @Operation(summary = "Re-send a history message")
+    @PostMapping("re-send")
+    public ResponseMessage<WebHookHistoryInfoVo> reSend(@RequestBody HookOneHistoryDto historyDto) {
+        return success(webHookHistoryService.reSend(historyDto, getLoginUser()));
+    }
+}
diff --git a/manager/tm/src/main/resources/init/dfs/version b/manager/tm/src/main/resources/init/dfs/version
index 0a06d1ecd8..283318df94 100644
--- a/manager/tm/src/main/resources/init/dfs/version
+++ b/manager/tm/src/main/resources/init/dfs/version
@@ -1,1 +1,1 @@
-3.5-3
\ No newline at end of file
+3.5-4
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/init/idaas/version b/manager/tm/src/main/resources/init/idaas/version
index f35c43ee7d..a172ed6375 100644
--- a/manager/tm/src/main/resources/init/idaas/version
+++ b/manager/tm/src/main/resources/init/idaas/version
@@ -1,1 +1,1 @@
-3.5-15
\ No newline at end of file
+3.5-16
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/messages.properties b/manager/tm/src/main/resources/messages.properties
index f569b2b905..4e7d896039 100644
--- a/manager/tm/src/main/resources/messages.properties
+++ b/manager/tm/src/main/resources/messages.properties
@@ -233,4 +233,23 @@ task.import.connection.check.ConnectorVersionTooHeight=Task import failed. The v
 
 # import task by relMig
 relMig.parse.failed=relmig file parsing failed, message: {0}
-relMig.parse.unSupport=The current relmig file version {0} is not currently supported, and only supports: {1}
\ No newline at end of file
+relMig.parse.unSupport=The current relmig file version {0} is not currently supported, and only supports: {1}
+
+#web hook
+webhook.create.hook.defaultName=<Default Service Hook Name>
+webhook.url.too.long=The maximum length allowed for URL to be filled in is: {0}, actual length filled in: {1}
+webhook.info.mark.too.long=The maximum length allowed for note text to be filled in is: {0}, actual length filled in: {1}
+webhook.info.custom.http.headers.too.long=The maximum length allowed for custom request header text is: {0}, actual length filled in: {1}
+webhook.info.custom.template.too.long=The maximum length allowed for filling in custom template text is: {0}, actual length filled in: {1}
+webhook.info.custom.template.invalid=The custom request body template is invalid
+webhook.info.custom.http.headers.invalid=The custom HTTP request header information is invalid, {0}
+webhook.ping.failed=Ping failed {0}, please check your WebHook URL
+webhook.ping.succeed=The testing PING event has been sent to the server. Please check the sending record to confirm the result
+webhook.url.host.invalid=Illegal IP and domain name configuration, unable to use the current system's IP port\uFF1A{0}
+webhook.url.invalid=Parameter verification failed, parameter attribute service URL format is incorrect. Actual value: {0} Expected type: Legal URI
+webhook.info.existsById=WebHook configuration information does not exist, WebHook ID: {0}
+webhook.history.reSend=Before re-send an history webhook event message, this history event ID must not be empty
+webhook.reOpen.failed=WebHook re-open failed, please retry again
+webhook.history.hook.id.error=WebHook ID must not be empty
+webhook.history.page.from.error=The paging parameter [pageFrom] is invalid, the starting value should be greater than or equal to 0, actual value is: {0}
+webhook.history.page.size.error=The paging parameter [pageSize] is invalid, the page size value should be greater than to 0, actual value is: {0}
diff --git a/manager/tm/src/main/resources/messages_en_US.properties b/manager/tm/src/main/resources/messages_en_US.properties
index 38c8b01c20..b4a8bed480 100644
--- a/manager/tm/src/main/resources/messages_en_US.properties
+++ b/manager/tm/src/main/resources/messages_en_US.properties
@@ -164,4 +164,23 @@ task.import.connection.check.ConnectorNotRegister=Task import failed. Please reg
 task.import.connection.check.ConnectorVersionTooHeight=Task import failed. The version of connector {0} used in the import task is too high. The current environment is using version {1}, and the version in the import task is {2}. Please upgrade connector {3} and import task again
 
 relMig.parse.failed=relmig file parsing failed, message: {0}
-relMig.parse.unSupport=The current relmig file version {0} is not currently supported, and only supports: {1}
\ No newline at end of file
+relMig.parse.unSupport=The current relmig file version {0} is not currently supported, and only supports: {1}
+
+#web hook
+webhook.create.hook.defaultName=<Default Service Hook Name>
+webhook.url.too.long=The maximum length allowed for URL to be filled in is: {0}, actual length filled in: {1}
+webhook.info.mark.too.long=The maximum length allowed for note text to be filled in is: {0}, actual length filled in: {1}
+webhook.info.custom.http.headers.too.long=The maximum length allowed for custom request header text is: {0}, actual length filled in: {1}
+webhook.info.custom.template.too.long=The maximum length allowed for filling in custom template text is: {0}, actual length filled in: {1}
+webhook.info.custom.template.invalid=The custom request body template is invalid
+webhook.info.custom.http.headers.invalid=The custom HTTP request header information is invalid, {0}
+webhook.ping.failed=Ping failed {0}, please check your WebHook URL
+webhook.url.host.invalid=Illegal IP and domain name configuration, unable to use the current system's IP port\uFF1A{0}
+webhook.ping.succeed=The testing PING event has been sent to the server. Please check the sending record to confirm the result
+webhook.url.invalid=Parameter verification failed, parameter attribute service_URL format is incorrect. Actual value: {0} Expected type: Legal URI
+webhook.info.existsById=WebHook configuration information does not exist, WebHook ID: {0}
+webhook.history.reSend=Before re-send an history webhook event message, this history event ID must not be empty
+webhook.reOpen.failed=WebHook re-open failed, please retry again
+webhook.history.hook.id.error=WebHook ID must not be empty
+webhook.history.page.from.error=The paging parameter [pageFrom] is invalid, the starting value should be greater than or equal to 0, actual value is: {0}
+webhook.history.page.size.error=The paging parameter [pageSize] is invalid, the page size value should be greater than to 0, actual value is: {0}
diff --git a/manager/tm/src/main/resources/messages_zh_CN.properties b/manager/tm/src/main/resources/messages_zh_CN.properties
index 3ca525050c..6be09d61d8 100644
--- a/manager/tm/src/main/resources/messages_zh_CN.properties
+++ b/manager/tm/src/main/resources/messages_zh_CN.properties
@@ -63,7 +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.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
@@ -264,4 +264,24 @@ task.import.connection.check.ConnectorNotRegister=\u4EFB\u52A1\u5BFC\u5165\u5931
 task.import.connection.check.ConnectorVersionTooHeight=\u4EFB\u52A1\u5BFC\u5165\u5931\u8D25\uFF0C\u5BFC\u5165\u4EFB\u52A1\u4E2D\u4F7F\u7528\u7684\u6570\u636E\u6E90 {0} \u7248\u672C\u8FC7\u9AD8\uFF0C\u5F53\u524D\u73AF\u5883\u4F7F\u7528\u7684\u7248\u672C\uFF1A{1}, \u5BFC\u5165\u4EFB\u52A1\u4E2D\u7684\u7248\u672C\uFF1A{2}, \u8BF7\u5347\u7EA7\u6570\u636E\u6E90 {3}\u540E\u91CD\u65B0\u5BFC\u5165
 
 relMig.parse.failed=relmig \u6587\u4EF6\u89E3\u6790\u5931\u8D25, \u9519\u8BEF\u539F\u56E0: {0}
-relMig.parse.unSupport=\u5F53\u524Drelmig\u6587\u4EF6\u7248\u672C{0}\u6682\u4E0D\u652F\u6301\uFF0C\u76EE\u524D\u4EC5\u652F\u6301\u652F\u6301\uFF1A{1}
\ No newline at end of file
+relMig.parse.unSupport=\u5F53\u524Drelmig\u6587\u4EF6\u7248\u672C{0}\u6682\u4E0D\u652F\u6301\uFF0C\u76EE\u524D\u4EC5\u652F\u6301\u652F\u6301\uFF1A{1}
+
+#web hook
+webhook.create.hook.defaultName=<\u9ED8\u8BA4Service Hook\u540D\u79F0>
+webhook.url.too.long=\u8BF7\u6C42URL\u5141\u8BB8\u586B\u5199\u7684\u6700\u5927\u957F\u5EA6\u662F\uFF1A{0}\uFF0C\u5B9E\u9645\u586B\u5199\u7684\u957F\u5EA6\uFF1A{1}
+webhook.info.mark.too.long=\u5907\u6CE8\u6587\u672C\u5141\u8BB8\u586B\u5199\u7684\u6700\u5927\u957F\u5EA6\u662F\uFF1A{0}\uFF0C\u5B9E\u9645\u586B\u5199\u7684\u957F\u5EA6\uFF1A{1}
+webhook.info.custom.http.headers.too.long=\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\u6587\u672C\u5141\u8BB8\u586B\u5199\u7684\u6700\u5927\u957F\u5EA6\u662F\uFF1A{0}\uFF0C\u5B9E\u9645\u586B\u5199\u7684\u957F\u5EA6\uFF1A{1}
+webhook.info.custom.template.too.long=\u81EA\u5B9A\u4E49\u6A21\u7248\u6587\u672C\u5141\u8BB8\u586B\u5199\u7684\u6700\u5927\u957F\u5EA6\u662F\uFF1A{0}\uFF0C\u5B9E\u9645\u586B\u5199\u7684\u957F\u5EA6\uFF1A{1}
+webhook.info.custom.template.invalid=\u81EA\u5B9A\u4E49\u8BF7\u6C42\u4F53\u6A21\u677F\u65E0\u6548
+webhook.info.custom.http.headers.invalid=\u81EA\u5B9A\u4E49HTTP\u8BF7\u6C42\u5934\u4FE1\u606F\u65E0\u6548\uFF0C{0}
+webhook.ping.failed=PING\u6D4B\u8BD5\u5931\u8D25 {0}, \u8BF7\u68C0\u67E5\u60A8\u7684 WebHook URL
+webhook.ping.succeed=\u5DF2\u53D1\u9001\u6D4B\u8BD5 PING \u4E8B\u4EF6\u5230\u670D\u52A1\u7AEF\uFF0C\u8BF7\u67E5\u770B\u53D1\u9001\u8BB0\u5F55\u786E\u8BA4\u7ED3\u679C
+webhook.url.host.invalid=\u975E\u6CD5\u7684IP\u548C\u57DF\u540D\u914D\u7F6E\uFF0C\u4E0D\u80FD\u4F7F\u7528\u5F53\u524D\u7CFB\u7EDF\u7684IP\u7AEF\u53E3\uFF1A{0}
+webhook.url.invalid=\u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF0C\u53C2\u6570\u5C5E\u6027 service_url \u683C\u5F0F\u4E0D\u6B63\u786E\u3002\u5B9E\u9645\u503C\uFF1A{0}\uFF0C\u671F\u671B\u7C7B\u578B\uFF1A\u5408\u6CD5\u7684 URI
+webhook.info.existsById=WebHook\u914D\u7F6E\u4FE1\u606F\u4E0D\u5B58\u5728\uFF0CWebHook ID: {0}
+webhook.history.reSend=\u5728\u91CD\u65B0\u53D1\u9001\u5386\u53F2 WebHook \u4E8B\u4EF6\u6D88\u606F\u4E4B\u524D\uFF0C\u6B64\u5386\u53F2\u4E8B\u4EF6ID\u4E0D\u5F97\u4E3A\u7A7A
+webhook.reOpen.failed=WebHook\u91CD\u65B0\u6253\u5F00\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5
+webhook.history.hook.id.error=WebHook ID \u4E0D\u80FD\u4E3A\u7A7A
+webhook.history.page.from.error=\u5206\u9875\u53C2\u6570[pageFrom]\u65E0\u6548\uFF0C\u8D77\u59CB\u503C\u5E94\u8BE5\u5927\u4E8E\u7B49\u4E8E0\uFF0C\u5B9E\u9645\u503C\u4E3A\uFF1A{0}
+webhook.history.page.size.error=\u5206\u9875\u53C2\u6570[pageSize]\u65E0\u6548\uFF0C\u6BCF\u9875\u5927\u5C0F\u5E94\u8BE5\u5927\u4E8E0\uFF0C\u5B9E\u9645\u503C\u4E3A\uFF1A{0}
+
diff --git a/manager/tm/src/main/resources/messages_zh_TW.properties b/manager/tm/src/main/resources/messages_zh_TW.properties
index 8a830ce1dd..677e23c916 100644
--- a/manager/tm/src/main/resources/messages_zh_TW.properties
+++ b/manager/tm/src/main/resources/messages_zh_TW.properties
@@ -101,4 +101,23 @@ task.import.connection.check.ConnectorNotRegister=\u4EFB\u52D9\u5C0E\u5165\u5931
 task.import.connection.check.ConnectorVersionTooHeight=\u4EFB\u52D9\u5C0E\u5165\u5931\u6557\uFF0C\u5C0E\u5165\u4EFB\u52D9\u4E2D\u4F7F\u7528\u7684\u6578\u64DA\u6E90 {0} \u7248\u672C\u904E\u9AD8\uFF0C\u7576\u524D\u74B0\u5883\u4F7F\u7528\u7684\u7248\u672C\uFF1A{1}\uFF0C\u5C0E\u5165\u4EFB\u52D9\u4E2D\u7684\u7248\u672C\uFF1A{2}\uFF0C\u8ACB\u965E\u7D1A\u6578\u64DA\u6E90 {3} \u5F8C\u91CD\u65B0\u5C0E\u5165
 
 relMig.parse.failed=relmig\u6A94\u6848\u89E3\u6790\u5931\u6557\uFF0C\u932F\u8AA4\u539F\u56E0\uFF1A{0}
-relMig.parse.unSupport=\u7576\u524Drelmig\u6A94\u6848\u7248\u672C{0}\u66AB\u4E0D\u652F\u6301\uFF0C\u73FE\u6642\u50C5\u652F\u6301\u652F\u6301\uFF1A{1}
\ No newline at end of file
+relMig.parse.unSupport=\u7576\u524Drelmig\u6A94\u6848\u7248\u672C{0}\u66AB\u4E0D\u652F\u6301\uFF0C\u73FE\u6642\u50C5\u652F\u6301\u652F\u6301\uFF1A{1}
+
+#web hook
+webhook.create.hook.defaultName=<\u9ED8\u8A8DService Hook\u540D\u7A31>
+webhook.url.too.long=\u8ACB\u6C42URL\u5141\u8A31\u586B\u5BEB\u7684\u6700\u5927\u9577\u5EA6\u662F\uFF1A{0}\uFF0C\u5BE6\u969B\u586B\u5BEB\u7684\u9577\u5EA6\uFF1A{1}
+webhook.info.mark.too.long=\u5099\u8A3B\u6587\u5B57\u5141\u8A31\u586B\u5BEB\u7684\u6700\u5927\u9577\u5EA6\u662F\uFF1A{0}\uFF0C\u5BE6\u969B\u586B\u5BEB\u7684\u9577\u5EA6\uFF1A{1}
+webhook.info.custom.http.headers.too.long=\u81EA\u5B9A\u7FA9\u8ACB\u6C42\u982D\u6587\u5B57\u5141\u8A31\u586B\u5BEB\u7684\u6700\u5927\u9577\u5EA6\u662F\uFF1A{0}\uFF0C\u5BE6\u969B\u586B\u5BEB\u7684\u9577\u5EA6\uFF1A{1}
+webhook.info.custom.template.too.long=\u81EA\u5B9A\u7FA9\u6A21\u7248\u6587\u5B57\u5141\u8A31\u586B\u5BEB\u7684\u6700\u5927\u9577\u5EA6\u662F\uFF1A{0}\uFF0C\u5BE6\u969B\u586B\u5BEB\u7684\u9577\u5EA6\uFF1A{1}
+webhook.info.custom.template.invalid=\u81EA\u5B9A\u7FA9\u8ACB\u6C42\u9AD4\u7BC4\u672C\u7121\u6548
+webhook.info.custom.http.headers.invalid=\u81EA\u5B9A\u7FA9Http\u8ACB\u6C42\u982D\u8CC7\u8A0A\u7121\u6548\uFF0C{0}
+webhook.ping.failed=PING\u6E2C\u8A66\u5931\u6557{0}\uFF0C\u8ACB\u6AA2\u67E5\u60A8\u7684WebHook URL
+webhook.url.host.invalid=\u975E\u6CD5\u7684IP\u548C\u529F\u80FD\u8B8A\u6578\u540D\u7A31\u914D\u5BD8\uFF0C\u4E0D\u80FD\u4F7F\u7528\u7576\u524D\u7CFB\u7D71\u7684IP\u57E0\uFF1A{0}
+webhook.ping.succeed=\u5DF2\u767C\u9001\u6E2C\u8A66PING\u4E8B\u4EF6\u5230\u670D\u52D9\u7AEF\uFF0C\u8ACB\u67E5\u770B\u767C\u9001\u8A18\u9304\u78BA\u8A8D\u7D50\u679C
+webhook.url.invalid=\u53C3\u6578\u6821\u9A57\u5931\u6557\uFF0C\u53C3\u6578\u5185\u5BB9service_url\u683C\u5F0F\u4E0D\u6B63\u78BA\u3002 \u5BE6\u969B\u503C\uFF1A {0}\uFF0C\u671F\u671B\u985E\u578B\uFF1A\u5408\u6CD5\u7684URI
+webhook.info.existsById=WebHook\u914D\u5BD8\u8CC7\u8A0A\u4E0D\u5B58\u5728\uFF0CWebHook ID: {0}
+webhook.history.reSend=\u5728\u91CD\u65B0\u767C\u9001\u6B77\u53F2 WebHook \u4E8B\u4EF6\u6D88\u606F\u4E4B\u524D\uFF0C\u6B64\u6B77\u53F2\u4E8B\u4EF6ID\u4E0D\u5F97\u70BA\u7A7A
+webhook.reOpen.failed=WebHook\u91CD\u65B0\u6253\u958B\u5931\u6557\uFF0C\u8ACB\u91CD\u8A66
+webhook.history.hook.id.error=WebHook ID \u4E0D\u80FD\u70BA\u7A7A
+webhook.history.page.from.error=\u5206\u9801\u53C3\u6578[pageFrom]\u7121\u6548\uFF0C\u8D77\u59CB\u503C\u61C9\u8A72\u5927\u65BC\u7B49\u65BC0\uFF0C\u5BE6\u969B\u503C\u70BA\uFF1A{0}
+webhook.history.page.size.error=\u5206\u9801\u53C3\u6578[pageSize]\u7121\u6548\uFF0C\u6BCF\u9801\u5927\u5C0F\u61C9\u8A72\u5927\u65BC0\uFF0C\u5BE6\u969B\u503C\u70BA\uFF1A{0}
diff --git a/pom.xml b/pom.xml
index 7ae4610a91..7adfb71861 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,23 +17,25 @@
         <sonar.organization>tapdata</sonar.organization>
         <sonar.host.url>https://sonarcloud.io</sonar.host.url>
         <sonar.coverage.exclusions>
-            manager/tm/**/dto/*,
-            manager/tm/**/entity/*,
-            manager/tm/**/vo/*,
+            manager/**/dto/*,
+            manager/**/entity/*,
+            manager/**/vo/*,
             manager/tm/**/controller/*,
-            manager/tm/**/constant/*,
+            manager/**/constant/*,
             manager/tm/**/ex/*,
             **/*Dto.java,
             **/*Enum.java,
             iengine/iengine-common/src/main/java/com/tapdata/entity/**,
             tapdata-cli/**,
             iengine/iengine-common/src/main/java/com/tapdata/exception/**,
-            manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/**
+            manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/**,
+            manager/tm/src/main/java/com/tapdata/tm/init/patches/daas/*,
+            manager/tm/src/main/java/com/tapdata/tm/init/patches/dfs/*
         </sonar.coverage.exclusions>
         <sonar.exclusions>
-            manager/tm/**/dto/*,
-            manager/tm/**/entity/*,
-            manager/tm/**/vo/*,
+            manager/**/dto/*,
+            manager/**/entity/*,
+            manager/**/vo/*,
             manager/tm/**/controller/*,
             manager/tm/**/constant/*,
             manager/tm/**/ex/*,
@@ -41,7 +43,9 @@
             **/*Enum.java,
             iengine/iengine-common/src/main/java/com/tapdata/entity/**,
             tapdata-cli/**,
-            manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/**
+            manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/**,
+            manager/tm/src/main/java/com/tapdata/tm/init/patches/daas/*,
+            manager/tm/src/main/java/com/tapdata/tm/init/patches/dfs/*
         </sonar.exclusions>
         <sonar.test.exclusions>
             **/src/test/**


Test patch:
diff --git a/iengine/api/src/test/java/io/tapdata/common/sharecdc/ShareCdcUtilTest.java b/iengine/api/src/test/java/io/tapdata/common/sharecdc/ShareCdcUtilTest.java
new file mode 100644
index 0000000000..190b775cbd
--- /dev/null
+++ b/iengine/api/src/test/java/io/tapdata/common/sharecdc/ShareCdcUtilTest.java
@@ -0,0 +1,73 @@
+package io.tapdata.common.sharecdc;
+
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.function.Function;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-05-16 22:20
+ **/
+class ShareCdcUtilTest {
+	@Nested
+	class iterateAndHandleSpecialTypeTest {
+
+		private Map<String, Object> map;
+		private Function<Object, Object> function;
+		private IllegalDatePredicate illegalDatePredicate;
+
+		@BeforeEach
+		void setUp() {
+			map = new HashMap<>();
+			map.put("_str", "test");
+			map.put("_int", 111);
+			Map<String, Object> subMap = new HashMap<>();
+			subMap.put("_str", "stest");
+			subMap.put("_int", 222);
+			map.put("_map", subMap);
+			List<Object> list = new ArrayList<>();
+			list.add("a");
+			list.add("b");
+			map.put("_list", list);
+			function = mock(Function.class);
+			when(function.apply(any())).thenAnswer(invocationOnMock -> invocationOnMock.getArgument(0));
+			illegalDatePredicate = mock(IllegalDatePredicate.class);
+		}
+
+		@Test
+		void testMainProcess() {
+			assertDoesNotThrow(() -> ShareCdcUtil.iterateAndHandleSpecialType(map, function));
+			verify(function, times(4)).apply(any());
+		}
+
+		@Test
+		void testIllegalDate() {
+			when(illegalDatePredicate.test("test")).thenReturn(true);
+			TapInsertRecordEvent tapInsertRecordEvent = TapInsertRecordEvent.create().init();
+			assertDoesNotThrow(() -> ShareCdcUtil.iterateAndHandleSpecialType(map, function, illegalDatePredicate, tapInsertRecordEvent, EventType.AFTER));
+			assertTrue(tapInsertRecordEvent.getContainsIllegalDate());
+			assertFalse(tapInsertRecordEvent.getAfterIllegalDateFieldName().isEmpty());
+			assertEquals(1, tapInsertRecordEvent.getAfterIllegalDateFieldName().size());
+			assertEquals("_str", tapInsertRecordEvent.getAfterIllegalDateFieldName().get(0));
+
+			TapUpdateRecordEvent tapUpdateRecordEvent = TapUpdateRecordEvent.create().init();
+			assertDoesNotThrow(() -> ShareCdcUtil.iterateAndHandleSpecialType(map, function, illegalDatePredicate, tapUpdateRecordEvent, EventType.BEFORE));
+			assertTrue(tapUpdateRecordEvent.getContainsIllegalDate());
+			assertFalse(tapUpdateRecordEvent.getBeforeIllegalDateFieldName().isEmpty());
+			assertEquals(1, tapUpdateRecordEvent.getBeforeIllegalDateFieldName().size());
+			assertEquals("_str", tapUpdateRecordEvent.getBeforeIllegalDateFieldName().get(0));
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastSchemaTargetNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastSchemaTargetNodeTest.java
index c72cef5951..2018beb628 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastSchemaTargetNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastSchemaTargetNodeTest.java
@@ -15,8 +15,17 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+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;
 
 /**
  * @author samuel
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNodeTest.java
index e2de8b0020..0108422b58 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNodeTest.java
@@ -2,8 +2,10 @@
 
 import base.hazelcast.BaseHazelcastNodeTest;
 import com.google.common.collect.Lists;
+import com.hazelcast.jet.core.Processor;
 import com.tapdata.entity.Connections;
 import com.tapdata.entity.TapdataEvent;
+import com.tapdata.entity.TapdataShareLogEvent;
 import com.tapdata.entity.task.context.DataProcessorContext;
 import com.tapdata.tm.commons.dag.DAG;
 import com.tapdata.tm.commons.dag.DmlPolicy;
@@ -16,19 +18,33 @@
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.entity.event.TapEvent;
+import io.tapdata.entity.event.dml.TapDeleteRecordEvent;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import io.tapdata.entity.event.dml.TapRecordEvent;
+import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 import io.tapdata.entity.schema.TapField;
 import io.tapdata.entity.schema.TapTable;
+import io.tapdata.metric.collector.ISyncMetricCollector;
 import io.tapdata.observable.logging.ObsLogger;
+import io.tapdata.pdk.apis.entity.Capability;
+import io.tapdata.pdk.apis.entity.ConnectionOptions;
 import io.tapdata.pdk.apis.entity.merge.MergeInfo;
 import io.tapdata.pdk.apis.entity.merge.MergeLookupResult;
 import io.tapdata.pdk.apis.functions.ConnectorFunctions;
 import io.tapdata.pdk.apis.functions.connector.target.CreateTableV2Function;
 import io.tapdata.pdk.core.api.ConnectorNode;
+import io.tapdata.pdk.core.async.AsyncUtils;
+import io.tapdata.pdk.core.async.ThreadPoolExecutorEx;
 import org.junit.jupiter.api.*;
+import org.mockito.MockedStatic;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.*;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
@@ -39,352 +55,613 @@
  * @create 2024-01-22 18:46
  **/
 @DisplayName("HazelcastTargetPdkBaseNode Class Test")
-public class HazelcastTargetPdkBaseNodeTest extends BaseHazelcastNodeTest {
-    private HazelcastTargetPdkBaseNode hazelcastTargetPdkBaseNode;
-
-    @BeforeEach
-    void setUp() {
-        hazelcastTargetPdkBaseNode = mock(HazelcastTargetPdkBaseNode.class);
-        when(hazelcastTargetPdkBaseNode.getDataProcessorContext()).thenReturn(dataProcessorContext);
-    }
-
-    @Nested
-    @DisplayName("fromTapValueMergeInfo method test")
-    class fromTapValueMergeInfoTest {
-        @BeforeEach
-        void setUp() {
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).fromTapValueMergeInfo(any());
-        }
-
-        @Test
-        @DisplayName("main process test")
-        void testMainProcess() {
-            MergeInfo mergeInfo = new MergeInfo();
-            TapdataEvent tapdataEvent = new TapdataEvent();
-            TapEvent tapEvent = mock(TapEvent.class);
-            when(tapEvent.getInfo(MergeInfo.EVENT_INFO_KEY)).thenReturn(mergeInfo);
-            tapdataEvent.setTapEvent(tapEvent);
-
-            hazelcastTargetPdkBaseNode.fromTapValueMergeInfo(tapdataEvent);
-            verify(hazelcastTargetPdkBaseNode, times(1)).recursiveMergeInfoTransformFromTapValue(any());
-        }
-
-        @Test
-        @DisplayName("when tapEvent not have mergeInfo")
-        void notHaveMergeInfo() {
-            TapdataEvent tapdataEvent = new TapdataEvent();
-            TapEvent tapEvent = mock(TapEvent.class);
-            when(tapEvent.getInfo(MergeInfo.EVENT_INFO_KEY)).thenReturn(null);
-            tapdataEvent.setTapEvent(tapEvent);
-
-            hazelcastTargetPdkBaseNode.fromTapValueMergeInfo(tapdataEvent);
-            verify(hazelcastTargetPdkBaseNode, times(0)).recursiveMergeInfoTransformFromTapValue(any());
-        }
-    }
-
-    @Nested
-    @DisplayName("recursiveMergeInfoTransformFromTapValue method test")
-    class recursiveMergeInfoTransformFromTapValueTest {
-        @BeforeEach
-        void setUp() {
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).recursiveMergeInfoTransformFromTapValue(any());
-        }
-
-        @Test
-        @DisplayName("main process test")
-        void testMainProcess() {
-            List<MergeLookupResult> mergeLookupResults = new ArrayList<>();
-            MergeLookupResult mergeLookupResult = new MergeLookupResult();
-            Map<String, Object> data = new HashMap<>();
-            data.put("_id", 1);
-            mergeLookupResult.setData(data);
-            mergeLookupResults.add(mergeLookupResult);
-            TapTable tapTable = new TapTable();
-            mergeLookupResult.setTapTable(tapTable);
-            List<MergeLookupResult> childMergeLookupResults = new ArrayList<>();
-            MergeLookupResult childMergeLookupResult = new MergeLookupResult();
-            Map<String, Object> childData = new HashMap<>(data);
-            childMergeLookupResult.setData(childData);
-            childMergeLookupResults.add(childMergeLookupResult);
-            childMergeLookupResult.setTapTable(tapTable);
-            TapCodecsFilterManager tapCodecsFilterManager = mock(TapCodecsFilterManager.class);
-            ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "codecsFilterManager", tapCodecsFilterManager);
-            mergeLookupResult.setMergeLookupResults(childMergeLookupResults);
-
-            hazelcastTargetPdkBaseNode.recursiveMergeInfoTransformFromTapValue(mergeLookupResults);
-            verify(hazelcastTargetPdkBaseNode, times(2)).fromTapValue(data, tapCodecsFilterManager, tapTable);
-        }
-
-        @Test
-        @DisplayName("when mergeLookupResults is empty")
-        void mergeLookupResultsIsEmpty() {
-            List<MergeLookupResult> mergeLookupResults = new ArrayList<>();
-
-            hazelcastTargetPdkBaseNode.recursiveMergeInfoTransformFromTapValue(mergeLookupResults);
-            verify(hazelcastTargetPdkBaseNode, times(0)).fromTapValue(any(Map.class), any(TapCodecsFilterManager.class), any(TapTable.class));
-        }
-    }
-
-    @DisplayName("test ignorePksAndIndices normal")
-    @Test
-    void ignorePksAndIndicesTest1() {
-        TapTable tapTable = new TapTable();
-        TapField field = getField("_id");
-        TapField index = getField("index");
-        tapTable.add(field);
-        tapTable.add(index);
-        List<String> list = Arrays.asList("_id", "index");
-
-        HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable, list);
-        LinkedHashMap<String, TapField> nameFieldMap = tapTable.getNameFieldMap();
-        TapField idField = nameFieldMap.get("_id");
-        TapField indexField = nameFieldMap.get("index");
-        assertEquals(2, nameFieldMap.size());
-        assertEquals(0, idField.getPrimaryKeyPos());
-        assertEquals(0, indexField.getPrimaryKeyPos());
-        assertEquals(false, indexField.getPrimaryKey());
-        assertEquals(false, idField.getPrimaryKey());
-    }
-
-    @DisplayName("test ignorePksAndIndices logic primary key is null")
-    @Test
-    void ignorePksAndIndicesTest2() {
-        TapTable tapTable = new TapTable();
-        TapField field = getField("_id");
-        TapField index = getField("index");
-        tapTable.add(field);
-        tapTable.add(index);
-        HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable, null);
-        LinkedHashMap<String, TapField> nameFieldMap = tapTable.getNameFieldMap();
-        TapField idField = nameFieldMap.get("_id");
-        TapField indexField = nameFieldMap.get("index");
-        assertEquals(2, nameFieldMap.size());
-        assertEquals(0, idField.getPrimaryKeyPos());
-        assertEquals(0, indexField.getPrimaryKeyPos());
-        assertEquals(false, indexField.getPrimaryKey());
-        assertEquals(false, idField.getPrimaryKey());
-    }
-
-    public TapField getField(String name) {
-        TapField field = new TapField();
-        field.setName(name);
-        return field;
-    }
-
-    class usePkAsUpdateConditionsTest {
-        @BeforeEach
-        void setUp() {
-            hazelcastTargetPdkBaseNode = spy(HazelcastTargetPdkBaseNode.class);
-        }
-
-        @DisplayName("test pk as update conditions same1")
-        @Test
-        void usePkAsUpdateConditionsTest1() {
-            assertTrue(hazelcastTargetPdkBaseNode.usePkAsUpdateConditions(Collections.emptyList(), null));
-        }
-
-        @DisplayName("test pk as update conditions same2")
-        @Test
-        void usePkAsUpdateConditionsTest2() {
-            assertTrue(hazelcastTargetPdkBaseNode.usePkAsUpdateConditions(Lists.newArrayList("A", "B"), Lists.newArrayList("A", "B")));
-        }
-
-        @DisplayName("test pk as update conditions different1")
-        @Test
-        void usePkAsUpdateConditionsTest3() {
-            assertFalse(hazelcastTargetPdkBaseNode.usePkAsUpdateConditions(Lists.newArrayList("A", "B"), Lists.newArrayList("C")));
-        }
-
-        @DisplayName("test pk as update conditions different2")
-        @Test
-        void usePkAsUpdateConditionsTest4() {
-            assertFalse(hazelcastTargetPdkBaseNode.usePkAsUpdateConditions(Lists.newArrayList("A", "B", "C"), Lists.newArrayList("A", "B")));
-        }
-    }
-
-    @Nested
-    class createTableTest{
-
-        DataProcessorContext dataProcessorContext;
-
-        @BeforeEach
-        void setUp() {
-            dataProcessorContext = mock(DataProcessorContext.class);
-            ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"dataProcessorContext",dataProcessorContext);
-            ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"clientMongoOperator",mockClientMongoOperator);
-        }
-        @Test
-        void testIsUnwindProcess(){
-            hazelcastTargetPdkBaseNode.unwindProcess = true;
-            TapTable tapTable = mock(TapTable.class);
-            AtomicBoolean atomicBoolean = new AtomicBoolean(false);
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable,atomicBoolean);
-            TableNode node = new TableNode();
-            node.setDisabled(false);
-            when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node)node);
-            ConnectorNode connectorNode = mock(ConnectorNode.class);
-            when(hazelcastTargetPdkBaseNode.getConnectorNode()).thenReturn(connectorNode);
-            ConnectorFunctions connectorFunctions = mock(ConnectorFunctions.class);
-            when(connectorNode.getConnectorFunctions()).thenReturn(connectorFunctions);
-            when(connectorFunctions.getCreateTableFunction()).thenReturn(null);
-            when(connectorFunctions.getCreateTableV2Function()).thenReturn(mock(CreateTableV2Function.class));
-            Connections connections = new Connections();
-            connections.setId("test");
-            when(dataProcessorContext.getTargetConn()).thenReturn(connections);
-            boolean result = hazelcastTargetPdkBaseNode.createTable(tapTable,atomicBoolean);
-            Assertions.assertTrue(result);
-        }
-
-        @Test
-        void testIsNotUnwindProcess(){
-            hazelcastTargetPdkBaseNode.unwindProcess = false;
-            TapTable tapTable = mock(TapTable.class);
-            AtomicBoolean atomicBoolean = new AtomicBoolean(false);
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable,atomicBoolean);
-            TableNode node = new TableNode();
-            node.setDisabled(false);
-            when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node)node);
-            ConnectorNode connectorNode = mock(ConnectorNode.class);
-            when(hazelcastTargetPdkBaseNode.getConnectorNode()).thenReturn(connectorNode);
-            ConnectorFunctions connectorFunctions = mock(ConnectorFunctions.class);
-            when(connectorNode.getConnectorFunctions()).thenReturn(connectorFunctions);
-            when(connectorFunctions.getCreateTableFunction()).thenReturn(null);
-            when(connectorFunctions.getCreateTableV2Function()).thenReturn(mock(CreateTableV2Function.class));
-            Connections connections = new Connections();
-            connections.setId("test");
-            when(dataProcessorContext.getTargetConn()).thenReturn(connections);
-            boolean result = hazelcastTargetPdkBaseNode.createTable(tapTable,atomicBoolean);
-            Assertions.assertTrue(result);
-        }
-    }
-
-    @Nested
-    class checkUnwindConfigurationTest{
-        DataProcessorContext dataProcessorContext;
-        ObsLogger obsLogger;
-
-        @BeforeEach
-        void setUp() {
-            dataProcessorContext = mock(DataProcessorContext.class);
-            obsLogger = mock(ObsLogger.class);
-            ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"dataProcessorContext",dataProcessorContext);
-            ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"obsLogger",obsLogger);
-
-        }
-        @Test
-        void test(){
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
-            TaskDto taskDto1 = mock(TaskDto.class);
-            DAG dag = mock(DAG.class);
-            taskDto1.setDag(dag);
-            List<Node> nodes = new ArrayList<>();
-            nodes.add(mock(UnwindProcessNode.class));
-            when(dataProcessorContext.getTaskDto()).thenReturn(taskDto1);
-            when(taskDto1.getDag()).thenReturn(dag);
-            when(dag.getNodes()).thenReturn(nodes);
-            TableNode node = new TableNode();
-            DmlPolicy dmlPolicy = new DmlPolicy();
-            dmlPolicy.setInsertPolicy(DmlPolicyEnum.just_insert);
-            node.setDmlPolicy(dmlPolicy);
-            when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node)node);
-            hazelcastTargetPdkBaseNode.checkUnwindConfiguration();
-            Assertions.assertTrue(hazelcastTargetPdkBaseNode.unwindProcess);
-        }
-
-        @Test
-        void testGetNodeIsNotTableNode(){
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
-            TaskDto taskDto1 = mock(TaskDto.class);
-            DAG dag = mock(DAG.class);
-            taskDto1.setDag(dag);
-            List<Node> nodes = new ArrayList<>();
-            nodes.add(mock(UnwindProcessNode.class));
-            when(dataProcessorContext.getTaskDto()).thenReturn(taskDto1);
-            when(taskDto1.getDag()).thenReturn(dag);
-            when(dag.getNodes()).thenReturn(nodes);
-            DatabaseNode node = new DatabaseNode();
-            DmlPolicy dmlPolicy = new DmlPolicy();
-            dmlPolicy.setInsertPolicy(DmlPolicyEnum.insert_on_nonexists);
-            node.setDmlPolicy(dmlPolicy);
-            when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node)node);
-            hazelcastTargetPdkBaseNode.checkUnwindConfiguration();
-            verify(obsLogger,times(0)).warn(any());
-        }
-
-        @Test
-        void testInsertPolicyIsNullJustInsert(){
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
-            TaskDto taskDto1 = mock(TaskDto.class);
-            DAG dag = mock(DAG.class);
-            taskDto1.setDag(dag);
-            List<Node> nodes = new ArrayList<>();
-            nodes.add(mock(UnwindProcessNode.class));
-            when(dataProcessorContext.getTaskDto()).thenReturn(taskDto1);
-            when(taskDto1.getDag()).thenReturn(dag);
-            when(dag.getNodes()).thenReturn(nodes);
-            TableNode node = new TableNode();
-            DmlPolicy dmlPolicy = new DmlPolicy();
-            dmlPolicy.setInsertPolicy(DmlPolicyEnum.insert_on_nonexists);
-            node.setDmlPolicy(dmlPolicy);
-            when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node)node);
-            hazelcastTargetPdkBaseNode.checkUnwindConfiguration();
-            verify(obsLogger,times(1)).warn(any());
-        }
-
-        @Test
-        void testDmlPolicyIsNull(){
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
-            TaskDto taskDto1 = mock(TaskDto.class);
-            DAG dag = mock(DAG.class);
-            taskDto1.setDag(dag);
-            List<Node> nodes = new ArrayList<>();
-            nodes.add(mock(UnwindProcessNode.class));
-            when(dataProcessorContext.getTaskDto()).thenReturn(taskDto1);
-            when(taskDto1.getDag()).thenReturn(dag);
-            when(dag.getNodes()).thenReturn(nodes);
-            TableNode node = new TableNode();
-            node.setDmlPolicy(null);
-            when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node)node);
-            hazelcastTargetPdkBaseNode.checkUnwindConfiguration();
-            verify(obsLogger,times(1)).warn(any());
-        }
-    }
-    @Nested
-    class IsCDCConcurrentTest{
-        ObsLogger obsLogger;
-        @BeforeEach
-        void setUp() {
-            obsLogger = mock(ObsLogger.class);
-            ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"cdcConcurrentWriteNum",8);
-            ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"obsLogger",obsLogger);
-        }
-        @Test
-        void testUnwindProcess(){
-            TableNode node = mock(TableNode.class);
-            List predecessors = new ArrayList<>();
-            UnwindProcessNode unwindProcessNode = new UnwindProcessNode();
-            predecessors.add(unwindProcessNode);
-            when(node.predecessors()).thenReturn(predecessors);
-            when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node)node);
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).isCDCConcurrent(true);
-            boolean result = hazelcastTargetPdkBaseNode.isCDCConcurrent(true);
-            Assertions.assertFalse(result);
-            verify(obsLogger,times(1)).info(any());
-        }
-
-        @Test
-        void testMergeTableNode(){
-            TableNode node = mock(TableNode.class);
-            List predecessors = new ArrayList<>();
-            MergeTableNode mergeTableNode = new MergeTableNode();
-            predecessors.add(mergeTableNode);
-            when(node.predecessors()).thenReturn(predecessors);
-            when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node)node);
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).isCDCConcurrent(true);
-            boolean result = hazelcastTargetPdkBaseNode.isCDCConcurrent(true);
-            Assertions.assertFalse(result);
-            verify(obsLogger,times(1)).info(any());
-        }
-    }
-
+class HazelcastTargetPdkBaseNodeTest extends BaseHazelcastNodeTest {
+	private HazelcastTargetPdkBaseNode hazelcastTargetPdkBaseNode;
+
+	@BeforeEach
+	void setUp() {
+		hazelcastTargetPdkBaseNode = mock(HazelcastTargetPdkBaseNode.class);
+		when(hazelcastTargetPdkBaseNode.getDataProcessorContext()).thenReturn(dataProcessorContext);
+	}
+
+	@Nested
+	@DisplayName("fromTapValueMergeInfo method test")
+	class fromTapValueMergeInfoTest {
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).fromTapValueMergeInfo(any());
+		}
+
+		@Test
+		@DisplayName("main process test")
+		void testMainProcess() {
+			MergeInfo mergeInfo = new MergeInfo();
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			TapEvent tapEvent = mock(TapEvent.class);
+			when(tapEvent.getInfo(MergeInfo.EVENT_INFO_KEY)).thenReturn(mergeInfo);
+			tapdataEvent.setTapEvent(tapEvent);
+
+			hazelcastTargetPdkBaseNode.fromTapValueMergeInfo(tapdataEvent);
+			verify(hazelcastTargetPdkBaseNode, times(1)).recursiveMergeInfoTransformFromTapValue(any());
+		}
+
+		@Test
+		@DisplayName("when tapEvent not have mergeInfo")
+		void notHaveMergeInfo() {
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			TapEvent tapEvent = mock(TapEvent.class);
+			when(tapEvent.getInfo(MergeInfo.EVENT_INFO_KEY)).thenReturn(null);
+			tapdataEvent.setTapEvent(tapEvent);
+
+			hazelcastTargetPdkBaseNode.fromTapValueMergeInfo(tapdataEvent);
+			verify(hazelcastTargetPdkBaseNode, times(0)).recursiveMergeInfoTransformFromTapValue(any());
+		}
+	}
+
+	@Nested
+	@DisplayName("recursiveMergeInfoTransformFromTapValue method test")
+	class recursiveMergeInfoTransformFromTapValueTest {
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).recursiveMergeInfoTransformFromTapValue(any());
+		}
+
+		@Test
+		@DisplayName("main process test")
+		void testMainProcess() {
+			List<MergeLookupResult> mergeLookupResults = new ArrayList<>();
+			MergeLookupResult mergeLookupResult = new MergeLookupResult();
+			Map<String, Object> data = new HashMap<>();
+			data.put("_id", 1);
+			mergeLookupResult.setData(data);
+			mergeLookupResults.add(mergeLookupResult);
+			TapTable tapTable = new TapTable();
+			mergeLookupResult.setTapTable(tapTable);
+			List<MergeLookupResult> childMergeLookupResults = new ArrayList<>();
+			MergeLookupResult childMergeLookupResult = new MergeLookupResult();
+			Map<String, Object> childData = new HashMap<>(data);
+			childMergeLookupResult.setData(childData);
+			childMergeLookupResults.add(childMergeLookupResult);
+			childMergeLookupResult.setTapTable(tapTable);
+			TapCodecsFilterManager tapCodecsFilterManager = mock(TapCodecsFilterManager.class);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "codecsFilterManager", tapCodecsFilterManager);
+			mergeLookupResult.setMergeLookupResults(childMergeLookupResults);
+
+			hazelcastTargetPdkBaseNode.recursiveMergeInfoTransformFromTapValue(mergeLookupResults);
+			verify(hazelcastTargetPdkBaseNode, times(2)).fromTapValue(data, tapCodecsFilterManager, tapTable);
+		}
+
+		@Test
+		@DisplayName("when mergeLookupResults is empty")
+		void mergeLookupResultsIsEmpty() {
+			List<MergeLookupResult> mergeLookupResults = new ArrayList<>();
+
+			hazelcastTargetPdkBaseNode.recursiveMergeInfoTransformFromTapValue(mergeLookupResults);
+			verify(hazelcastTargetPdkBaseNode, times(0)).fromTapValue(any(Map.class), any(TapCodecsFilterManager.class), any(TapTable.class));
+		}
+	}
+
+	@DisplayName("test ignorePksAndIndices normal")
+	@Test
+	void ignorePksAndIndicesTest1() {
+		TapTable tapTable = new TapTable();
+		TapField field = getField("_id");
+		TapField index = getField("index");
+		tapTable.add(field);
+		tapTable.add(index);
+		List<String> list = Arrays.asList("_id", "index");
+
+		HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable, list);
+		LinkedHashMap<String, TapField> nameFieldMap = tapTable.getNameFieldMap();
+		TapField idField = nameFieldMap.get("_id");
+		TapField indexField = nameFieldMap.get("index");
+		assertEquals(2, nameFieldMap.size());
+		assertEquals(0, idField.getPrimaryKeyPos());
+		assertEquals(0, indexField.getPrimaryKeyPos());
+		assertEquals(false, indexField.getPrimaryKey());
+		assertEquals(false, idField.getPrimaryKey());
+	}
+
+	@DisplayName("test ignorePksAndIndices logic primary key is null")
+	@Test
+	void ignorePksAndIndicesTest2() {
+		TapTable tapTable = new TapTable();
+		TapField field = getField("_id");
+		TapField index = getField("index");
+		tapTable.add(field);
+		tapTable.add(index);
+		HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable, null);
+		LinkedHashMap<String, TapField> nameFieldMap = tapTable.getNameFieldMap();
+		TapField idField = nameFieldMap.get("_id");
+		TapField indexField = nameFieldMap.get("index");
+		assertEquals(2, nameFieldMap.size());
+		assertEquals(0, idField.getPrimaryKeyPos());
+		assertEquals(0, indexField.getPrimaryKeyPos());
+		assertEquals(false, indexField.getPrimaryKey());
+		assertEquals(false, idField.getPrimaryKey());
+	}
+
+	public TapField getField(String name) {
+		TapField field = new TapField();
+		field.setName(name);
+		return field;
+	}
+
+	@Nested
+	class usePkAsUpdateConditionsTest {
+		@BeforeEach
+		void setUp() {
+			when(hazelcastTargetPdkBaseNode.usePkAsUpdateConditions(any(), any())).thenCallRealMethod();
+		}
+
+		@DisplayName("test pk as update conditions same1")
+		@Test
+		void usePkAsUpdateConditionsTest1() {
+			assertTrue(hazelcastTargetPdkBaseNode.usePkAsUpdateConditions(Collections.emptyList(), null));
+		}
+
+		@DisplayName("test pk as update conditions same2")
+		@Test
+		void usePkAsUpdateConditionsTest2() {
+			assertTrue(hazelcastTargetPdkBaseNode.usePkAsUpdateConditions(Lists.newArrayList("A", "B"), Lists.newArrayList("A", "B")));
+		}
+
+		@DisplayName("test pk as update conditions different1")
+		@Test
+		void usePkAsUpdateConditionsTest3() {
+			assertFalse(hazelcastTargetPdkBaseNode.usePkAsUpdateConditions(Lists.newArrayList("A", "B"), Lists.newArrayList("C")));
+		}
+
+		@DisplayName("test pk as update conditions different2")
+		@Test
+		void usePkAsUpdateConditionsTest4() {
+			assertFalse(hazelcastTargetPdkBaseNode.usePkAsUpdateConditions(Lists.newArrayList("A", "B", "C"), Lists.newArrayList("A", "B")));
+		}
+	}
+
+	@Nested
+	class createTableTest {
+
+		DataProcessorContext dataProcessorContext;
+
+		@BeforeEach
+		void setUp() {
+			dataProcessorContext = mock(DataProcessorContext.class);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "dataProcessorContext", dataProcessorContext);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "clientMongoOperator", mockClientMongoOperator);
+		}
+
+		@Test
+		void testIsUnwindProcess() {
+			hazelcastTargetPdkBaseNode.unwindProcess = true;
+			TapTable tapTable = mock(TapTable.class);
+			AtomicBoolean atomicBoolean = new AtomicBoolean(false);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable, atomicBoolean);
+			TableNode node = new TableNode();
+			node.setDisabled(false);
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
+			ConnectorNode connectorNode = mock(ConnectorNode.class);
+			when(hazelcastTargetPdkBaseNode.getConnectorNode()).thenReturn(connectorNode);
+			ConnectorFunctions connectorFunctions = mock(ConnectorFunctions.class);
+			when(connectorNode.getConnectorFunctions()).thenReturn(connectorFunctions);
+			when(connectorFunctions.getCreateTableFunction()).thenReturn(null);
+			when(connectorFunctions.getCreateTableV2Function()).thenReturn(mock(CreateTableV2Function.class));
+			Connections connections = new Connections();
+			connections.setId("test");
+			when(dataProcessorContext.getTargetConn()).thenReturn(connections);
+			boolean result = hazelcastTargetPdkBaseNode.createTable(tapTable, atomicBoolean);
+			Assertions.assertTrue(result);
+		}
+
+		@Test
+		void testIsNotUnwindProcess() {
+			hazelcastTargetPdkBaseNode.unwindProcess = false;
+			TapTable tapTable = mock(TapTable.class);
+			AtomicBoolean atomicBoolean = new AtomicBoolean(false);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable, atomicBoolean);
+			TableNode node = new TableNode();
+			node.setDisabled(false);
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
+			ConnectorNode connectorNode = mock(ConnectorNode.class);
+			when(hazelcastTargetPdkBaseNode.getConnectorNode()).thenReturn(connectorNode);
+			ConnectorFunctions connectorFunctions = mock(ConnectorFunctions.class);
+			when(connectorNode.getConnectorFunctions()).thenReturn(connectorFunctions);
+			when(connectorFunctions.getCreateTableFunction()).thenReturn(null);
+			when(connectorFunctions.getCreateTableV2Function()).thenReturn(mock(CreateTableV2Function.class));
+			Connections connections = new Connections();
+			connections.setId("test");
+			when(dataProcessorContext.getTargetConn()).thenReturn(connections);
+			boolean result = hazelcastTargetPdkBaseNode.createTable(tapTable, atomicBoolean);
+			Assertions.assertTrue(result);
+		}
+	}
+
+	@Nested
+	class checkUnwindConfigurationTest {
+		DataProcessorContext dataProcessorContext;
+		ObsLogger obsLogger;
+
+		@BeforeEach
+		void setUp() {
+			dataProcessorContext = mock(DataProcessorContext.class);
+			obsLogger = mock(ObsLogger.class);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "dataProcessorContext", dataProcessorContext);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "obsLogger", obsLogger);
+
+		}
+
+		@Test
+		void test() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
+			TaskDto taskDto1 = mock(TaskDto.class);
+			DAG dag = mock(DAG.class);
+			taskDto1.setDag(dag);
+			List<Node> nodes = new ArrayList<>();
+			nodes.add(mock(UnwindProcessNode.class));
+			when(dataProcessorContext.getTaskDto()).thenReturn(taskDto1);
+			when(taskDto1.getDag()).thenReturn(dag);
+			when(dag.getNodes()).thenReturn(nodes);
+			TableNode node = new TableNode();
+			DmlPolicy dmlPolicy = new DmlPolicy();
+			dmlPolicy.setInsertPolicy(DmlPolicyEnum.just_insert);
+			node.setDmlPolicy(dmlPolicy);
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
+			hazelcastTargetPdkBaseNode.checkUnwindConfiguration();
+			Assertions.assertTrue(hazelcastTargetPdkBaseNode.unwindProcess);
+		}
+
+		@Test
+		void testGetNodeIsNotTableNode() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
+			TaskDto taskDto1 = mock(TaskDto.class);
+			DAG dag = mock(DAG.class);
+			taskDto1.setDag(dag);
+			List<Node> nodes = new ArrayList<>();
+			nodes.add(mock(UnwindProcessNode.class));
+			when(dataProcessorContext.getTaskDto()).thenReturn(taskDto1);
+			when(taskDto1.getDag()).thenReturn(dag);
+			when(dag.getNodes()).thenReturn(nodes);
+			DatabaseNode node = new DatabaseNode();
+			DmlPolicy dmlPolicy = new DmlPolicy();
+			dmlPolicy.setInsertPolicy(DmlPolicyEnum.insert_on_nonexists);
+			node.setDmlPolicy(dmlPolicy);
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
+			hazelcastTargetPdkBaseNode.checkUnwindConfiguration();
+			verify(obsLogger, times(0)).warn(any());
+		}
+
+		@Test
+		void testInsertPolicyIsNullJustInsert() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
+			TaskDto taskDto1 = mock(TaskDto.class);
+			DAG dag = mock(DAG.class);
+			taskDto1.setDag(dag);
+			List<Node> nodes = new ArrayList<>();
+			nodes.add(mock(UnwindProcessNode.class));
+			when(dataProcessorContext.getTaskDto()).thenReturn(taskDto1);
+			when(taskDto1.getDag()).thenReturn(dag);
+			when(dag.getNodes()).thenReturn(nodes);
+			TableNode node = new TableNode();
+			DmlPolicy dmlPolicy = new DmlPolicy();
+			dmlPolicy.setInsertPolicy(DmlPolicyEnum.insert_on_nonexists);
+			node.setDmlPolicy(dmlPolicy);
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
+			hazelcastTargetPdkBaseNode.checkUnwindConfiguration();
+			verify(obsLogger, times(1)).warn(any());
+		}
+
+		@Test
+		void testDmlPolicyIsNull() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
+			TaskDto taskDto1 = mock(TaskDto.class);
+			DAG dag = mock(DAG.class);
+			taskDto1.setDag(dag);
+			List<Node> nodes = new ArrayList<>();
+			nodes.add(mock(UnwindProcessNode.class));
+			when(dataProcessorContext.getTaskDto()).thenReturn(taskDto1);
+			when(taskDto1.getDag()).thenReturn(dag);
+			when(dag.getNodes()).thenReturn(nodes);
+			TableNode node = new TableNode();
+			node.setDmlPolicy(null);
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
+			hazelcastTargetPdkBaseNode.checkUnwindConfiguration();
+			verify(obsLogger, times(1)).warn(any());
+		}
+	}
+
+	@Nested
+	class IsCDCConcurrentTest {
+		ObsLogger obsLogger;
+
+		@BeforeEach
+		void setUp() {
+			obsLogger = mock(ObsLogger.class);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "cdcConcurrentWriteNum", 8);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "obsLogger", obsLogger);
+		}
+
+		@Test
+		void testUnwindProcess() {
+			TableNode node = mock(TableNode.class);
+			List predecessors = new ArrayList<>();
+			UnwindProcessNode unwindProcessNode = new UnwindProcessNode();
+			predecessors.add(unwindProcessNode);
+			when(node.predecessors()).thenReturn(predecessors);
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).isCDCConcurrent(true);
+			boolean result = hazelcastTargetPdkBaseNode.isCDCConcurrent(true);
+			Assertions.assertFalse(result);
+			verify(obsLogger, times(1)).info(any());
+		}
+
+		@Test
+		void testMergeTableNode() {
+			TableNode node = mock(TableNode.class);
+			List predecessors = new ArrayList<>();
+			MergeTableNode mergeTableNode = new MergeTableNode();
+			predecessors.add(mergeTableNode);
+			when(node.predecessors()).thenReturn(predecessors);
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).isCDCConcurrent(true);
+			boolean result = hazelcastTargetPdkBaseNode.isCDCConcurrent(true);
+			Assertions.assertFalse(result);
+			verify(obsLogger, times(1)).info(any());
+		}
+	}
+
+	@Nested
+	class HandleTapdataShareLogEventTest {
+		List<TapdataShareLogEvent> tapdataShareLogEvents;
+		TapdataEvent tapdataEvent;
+		Consumer<TapdataEvent> consumer;
+
+		@Test
+		@DisplayName("test handleTapdataShareLogEvent method for TapRecordEvent")
+		void test1() {
+			tapdataShareLogEvents = new ArrayList<>();
+			tapdataEvent = new TapdataShareLogEvent();
+			TapRecordEvent event = new TapInsertRecordEvent();
+			tapdataEvent.setTapEvent(event);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataShareLogEvent(tapdataShareLogEvents, tapdataEvent, consumer);
+			hazelcastTargetPdkBaseNode.handleTapdataShareLogEvent(tapdataShareLogEvents, tapdataEvent, consumer);
+			verify(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(any(TapRecordEvent.class));
+		}
+	}
+
+	@Nested
+	class HandleTapdataRecordEventTest {
+		private TapdataEvent tapdataEvent = new TapdataEvent();
+
+		@BeforeEach
+		void beforeEach() {
+		}
+
+		@Test
+		@DisplayName("test handleTapdataRecordEvent method for TapUpdateRecordEvent")
+		void test1() {
+			String writeStrategy = "appendWrite";
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "writeStrategy", writeStrategy);
+			TapRecordEvent tapRecordEvent = new TapUpdateRecordEvent();
+			Map<String, Object> after = new HashMap<>();
+			((TapUpdateRecordEvent) tapRecordEvent).setAfter(after);
+			tapdataEvent.setTapEvent(tapRecordEvent);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataRecordEvent(tapdataEvent);
+			hazelcastTargetPdkBaseNode.handleTapdataRecordEvent(tapdataEvent);
+			verify(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(any(TapRecordEvent.class));
+		}
+
+		@Test
+		@DisplayName("test handleTapdataRecordEvent method for TapDeleteRecordEvent")
+		void test2() {
+			String writeStrategy = "appendWrite";
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "writeStrategy", writeStrategy);
+			TapRecordEvent tapRecordEvent = new TapDeleteRecordEvent();
+			Map<String, Object> after = new HashMap<>();
+			((TapDeleteRecordEvent) tapRecordEvent).setBefore(after);
+			tapdataEvent.setTapEvent(tapRecordEvent);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataRecordEvent(tapdataEvent);
+			hazelcastTargetPdkBaseNode.handleTapdataRecordEvent(tapdataEvent);
+			verify(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(any(TapRecordEvent.class));
+		}
+	}
+
+	@Nested
+	class ReplaceIllegalDateWithNullIfNeedTest {
+		private TapRecordEvent event;
+		private List<Capability> capabilities;
+		private Connections connections;
+
+		@BeforeEach
+		void beforeEach() {
+			dataProcessorContext = mock(DataProcessorContext.class);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "dataProcessorContext", dataProcessorContext);
+			connections = mock(Connections.class);
+			when(dataProcessorContext.getConnections()).thenReturn(connections);
+			capabilities = new ArrayList<>();
+			Capability capability = mock(Capability.class);
+			capabilities.add(capability);
+			when(connections.getCapabilities()).thenReturn(capabilities);
+			when(capability.getId()).thenReturn(ConnectionOptions.DML_INSERT_POLICY_UPDATE_ON_EXISTS);
+		}
+
+		@Test
+		@DisplayName("test replaceIllegalDateWithNullIfNeed method when containsIllegalDate is false")
+		void test1() {
+			event = new TapInsertRecordEvent();
+			event.setContainsIllegalDate(true);
+			Map<String, Object> after = new HashMap<>();
+			after.put("id", "1");
+			after.put("name", "test");
+			((TapInsertRecordEvent) event).setAfter(after);
+			event.setContainsIllegalDate(false);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(event);
+			hazelcastTargetPdkBaseNode.replaceIllegalDateWithNullIfNeed(event);
+			assertEquals(after, ((TapInsertRecordEvent) event).getAfter());
+		}
+
+		@Test
+		@DisplayName("test replaceIllegalDateWithNullIfNeed method when containsIllegalDate is true for TapInsertRecordEvent")
+		void test2() {
+			event = new TapInsertRecordEvent();
+			event.setContainsIllegalDate(true);
+			Map<String, Object> after = new HashMap<>();
+			after.put("id", "1");
+			after.put("name", "test");
+			after.put("date", new Date());
+			after.put("last_date", new Date());
+			((TapInsertRecordEvent) event).setAfter(after);
+			event.setContainsIllegalDate(true);
+			List<String> illegalDateFiledName = new ArrayList<>();
+			illegalDateFiledName.add("date");
+			illegalDateFiledName.add("last_date");
+			illegalDateFiledName.add("test_date");
+			((TapInsertRecordEvent) event).setAfterIllegalDateFieldName(illegalDateFiledName);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(event);
+			hazelcastTargetPdkBaseNode.replaceIllegalDateWithNullIfNeed(event);
+			assertNull(((TapInsertRecordEvent) event).getAfter().get("date"));
+			assertNull(((TapInsertRecordEvent) event).getAfter().get("last_date"));
+		}
+
+		@Test
+		@DisplayName("test replaceIllegalDateWithNullIfNeed method when containsIllegalDate is true for TapUpdateRecordEvent")
+		void test3() {
+			event = new TapUpdateRecordEvent();
+			event.setContainsIllegalDate(true);
+			Map<String, Object> after = new HashMap<>();
+			after.put("id", "1");
+			after.put("name", "test");
+			after.put("date", new Date());
+			((TapUpdateRecordEvent) event).setBefore(after);
+			((TapUpdateRecordEvent) event).setAfter(after);
+			event.setContainsIllegalDate(true);
+			List<String> illegalDateFiledName = new ArrayList<>();
+			illegalDateFiledName.add("date");
+			illegalDateFiledName.add("last_date");
+			((TapUpdateRecordEvent) event).setBeforeIllegalDateFieldName(illegalDateFiledName);
+			((TapUpdateRecordEvent) event).setAfterIllegalDateFieldName(illegalDateFiledName);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(event);
+			hazelcastTargetPdkBaseNode.replaceIllegalDateWithNullIfNeed(event);
+			assertNull(((TapUpdateRecordEvent) event).getBefore().get("date"));
+			assertNull(((TapUpdateRecordEvent) event).getAfter().get("date"));
+		}
+
+		@Test
+		@DisplayName("test replaceIllegalDateWithNullIfNeed method when containsIllegalDate is true for TapDeleteRecordEvent")
+		void test4() {
+			event = new TapDeleteRecordEvent();
+			event.setContainsIllegalDate(true);
+			Map<String, Object> before = new HashMap<>();
+			((TapDeleteRecordEvent) event).setBefore(before);
+			event.setContainsIllegalDate(true);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(event);
+			hazelcastTargetPdkBaseNode.replaceIllegalDateWithNullIfNeed(event);
+			assertEquals(before, ((TapDeleteRecordEvent) event).getBefore());
+		}
+
+		@Test
+		@DisplayName("test replaceIllegalDateWithNullIfNeed method when illegalDateAcceptable is true")
+		void test5() {
+			hazelcastTargetPdkBaseNode.illegalDateAcceptable = true;
+			event = new TapInsertRecordEvent();
+			event.setContainsIllegalDate(true);
+			Map<String, Object> after = new HashMap<>();
+			after.put("date", new Date());
+			after.put("last_date", new Date());
+			((TapInsertRecordEvent) event).setAfter(after);
+			event.setContainsIllegalDate(true);
+			List<String> illegalDateFiledName = new ArrayList<>();
+			illegalDateFiledName.add("date");
+			illegalDateFiledName.add("last_date");
+			((TapInsertRecordEvent) event).setAfterIllegalDateFieldName(illegalDateFiledName);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(event);
+			hazelcastTargetPdkBaseNode.replaceIllegalDateWithNullIfNeed(event);
+			assertNotNull(((TapInsertRecordEvent) event).getAfter().get("date"));
+			assertNotNull(((TapInsertRecordEvent) event).getAfter().get("last_date"));
+		}
+	}
+
+	@Nested
+	@DisplayName("Method initIllegalDateAcceptable test")
+	class initIllegalDateAcceptableTest {
+
+		private Connections connections;
+
+		@BeforeEach
+		void setUp() {
+			connections = new Connections();
+			dataProcessorContext = new DataProcessorContext.DataProcessorContextBuilder()
+					.withConnections(connections).build();
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "dataProcessorContext", dataProcessorContext);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).initIllegalDateAcceptable();
+		}
+
+		@Test
+		@DisplayName("test connector can accept illegal date")
+		void testIllegalDateAcceptable() {
+			List<Capability> capabilities = new ArrayList<>();
+			capabilities.add(new Capability(ConnectionOptions.DML_ILLEGAL_DATE_ACCEPTABLE));
+			connections.setCapabilities(capabilities);
+			hazelcastTargetPdkBaseNode.initIllegalDateAcceptable();
+			assertTrue(hazelcastTargetPdkBaseNode.illegalDateAcceptable);
+		}
+
+		@Test
+		@DisplayName("test connector cannot accept illegal date")
+		void testNotIllegalDateAcceptable() {
+			hazelcastTargetPdkBaseNode.initIllegalDateAcceptable();
+			assertFalse(hazelcastTargetPdkBaseNode.illegalDateAcceptable);
+
+			List<Capability> capabilities = new ArrayList<>();
+			connections.setCapabilities(capabilities);
+			hazelcastTargetPdkBaseNode.initIllegalDateAcceptable();
+			assertFalse(hazelcastTargetPdkBaseNode.illegalDateAcceptable);
+		}
+	}
+
+	@Nested
+	class doInitTest {
+
+		private ScheduledExecutorService flushOffsetExecutor;
+
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).doInit(any(Processor.Context.class));
+			ThreadPoolExecutorEx queueConsumerThreadPool = AsyncUtils.createThreadPoolExecutor("test", 1, new ThreadGroup("test"), HazelcastTargetPdkBaseNodeTest.class.getSimpleName());
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "queueConsumerThreadPool", queueConsumerThreadPool);
+			flushOffsetExecutor = new ScheduledThreadPoolExecutor(1, r -> {
+				Thread thread = new Thread(r);
+				thread.setName("test");
+				return thread;
+			});
+			flushOffsetExecutor = spy(flushOffsetExecutor);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "flushOffsetExecutor", flushOffsetExecutor);
+			Node node = new TableNode();
+			node.setId("1");
+			node.setName("test");
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn(node);
+			dataProcessorContext = new DataProcessorContext.DataProcessorContextBuilder().build();
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "dataProcessorContext", dataProcessorContext);
+		}
+
+		@Test
+		void testMainProcess() {
+			try (
+					MockedStatic<ISyncMetricCollector> iSyncMetricCollectorMockedStatic = mockStatic(ISyncMetricCollector.class)
+			) {
+				hazelcastTargetPdkBaseNode.doInit(mock(Processor.Context.class));
+				iSyncMetricCollectorMockedStatic.verify(() -> ISyncMetricCollector.init(dataProcessorContext));
+				verify(hazelcastTargetPdkBaseNode).doInit(any());
+				verify(hazelcastTargetPdkBaseNode).createPdkAndInit(any(Processor.Context.class));
+				verify(hazelcastTargetPdkBaseNode).initExactlyOnceWriteIfNeed();
+				verify(hazelcastTargetPdkBaseNode).initTargetVariable();
+				verify(hazelcastTargetPdkBaseNode).initTargetQueueConsumer();
+				verify(hazelcastTargetPdkBaseNode).initTargetConcurrentProcessorIfNeed();
+				verify(hazelcastTargetPdkBaseNode).initTapEventFilter();
+				verify(hazelcastTargetPdkBaseNode).initIllegalDateAcceptable();
+				verify(flushOffsetExecutor).scheduleWithFixedDelay(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class));
+				assertTrue(Thread.currentThread().getName().startsWith("Target-Process"));
+				verify(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
+			}
+		}
+	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkDataNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkDataNodeTest.java
index a444c5397a..15f2088a5c 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkDataNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkDataNodeTest.java
@@ -25,16 +25,11 @@
 import io.tapdata.schema.TapTableMap;
 import lombok.SneakyThrows;
 import org.jetbrains.annotations.NotNull;
-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.internal.verification.Times;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.Callable;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.function.Consumer;
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkShareCDCNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkShareCDCNodeTest.java
index fe92b1118c..ad02f4ea93 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkShareCDCNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkShareCDCNodeTest.java
@@ -1,13 +1,21 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
 
+import com.hazelcast.jet.core.Processor;
 import com.tapdata.entity.TapdataShareLogEvent;
 import com.tapdata.entity.sharecdc.LogContent;
+import com.tapdata.entity.task.context.DataProcessorContext;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.nodes.TableNode;
 import io.tapdata.common.sharecdc.ShareCdcUtil;
 import io.tapdata.construct.HazelcastConstruct;
+import io.tapdata.entity.TapProcessorNodeContext;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 import io.tapdata.error.TaskProcessorExCode_11;
 import io.tapdata.exception.TapCodeException;
+import io.tapdata.node.pdk.processor.TapProcessorNode;
+import io.tapdata.pdk.core.async.AsyncUtils;
+import io.tapdata.pdk.core.async.ThreadPoolExecutorEx;
 import lombok.SneakyThrows;
 import org.bson.Document;
 import org.junit.jupiter.api.BeforeEach;
@@ -15,9 +23,12 @@
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
@@ -37,4 +48,51 @@ void setUp() {
 		hazelcastTargetPdkShareCDCNode = mock(HazelcastTargetPdkShareCDCNode.class);
 	}
 
+	@Nested
+	@DisplayName("Method doInit test")
+	class doInitTest {
+
+		private Processor.Context context;
+		private ScheduledExecutorService flushOffsetExecutor;
+		private DataProcessorContext dataProcessorContext;
+
+		@BeforeEach
+		void setUp() {
+			context = mock(Processor.Context.class);
+			doCallRealMethod().when(hazelcastTargetPdkShareCDCNode).doInit(context);
+			ThreadPoolExecutorEx queueConsumerThreadPool = AsyncUtils.createThreadPoolExecutor("test", 1, new ThreadGroup("test"), HazelcastTargetPdkBaseNodeTest.class.getSimpleName());
+			ReflectionTestUtils.setField(hazelcastTargetPdkShareCDCNode, "queueConsumerThreadPool", queueConsumerThreadPool);
+			flushOffsetExecutor = new ScheduledThreadPoolExecutor(1, r -> {
+				Thread thread = new Thread(r);
+				thread.setName("test");
+				return thread;
+			});
+			flushOffsetExecutor = spy(flushOffsetExecutor);
+			ReflectionTestUtils.setField(hazelcastTargetPdkShareCDCNode, "flushOffsetExecutor", flushOffsetExecutor);
+			Node node = new TableNode();
+			node.setId("1");
+			node.setName("test");
+			when(hazelcastTargetPdkShareCDCNode.getNode()).thenReturn(node);
+			dataProcessorContext = new DataProcessorContext.DataProcessorContextBuilder().build();
+			ReflectionTestUtils.setField(hazelcastTargetPdkShareCDCNode, "dataProcessorContext", dataProcessorContext);
+		}
+
+		@Test
+		@DisplayName("test main process")
+		void testMainProcess() {
+			TapProcessorNodeContext tapProcessorNodeContext = mock(TapProcessorNodeContext.class);
+			when(hazelcastTargetPdkShareCDCNode.initTapProcessorNodeContext()).thenReturn(tapProcessorNodeContext);
+			TapProcessorNode processorNode = mock(TapProcessorNode.class);
+			ReflectionTestUtils.setField(hazelcastTargetPdkShareCDCNode, "processorNode", processorNode);
+
+			hazelcastTargetPdkShareCDCNode.doInit(context);
+
+			verify(hazelcastTargetPdkShareCDCNode).doInit(context);
+			verify(hazelcastTargetPdkShareCDCNode).initTapProcessorNodeContext();
+			assertEquals(10000, hazelcastTargetPdkShareCDCNode.targetBatch);
+			assertEquals(1000, hazelcastTargetPdkShareCDCNode.targetBatchIntervalMs);
+			verify(processorNode).doInit(context, tapProcessorNodeContext);
+			assertTrue(hazelcastTargetPdkShareCDCNode.illegalDateAcceptable);
+		}
+	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessorTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessorTest.java
index 75b4c3cb9b..40fb35c76b 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessorTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessorTest.java
@@ -1,6 +1,8 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.concurrent;
 
 import com.tapdata.entity.*;
+import com.tapdata.entity.dataflow.TableBatchReadStatus;
+import com.tapdata.entity.dataflow.batch.BatchOffset;
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.ddl.table.TapNewFieldEvent;
@@ -27,6 +29,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -626,6 +629,44 @@ void testOther() throws InterruptedException {
         assertTrue(Thread.currentThread().isInterrupted());
     }
 
+    @Test
+    @SneakyThrows
+    void testProcessOffset() {
+        AtomicReference<TapdataEvent> offsetEvent = new AtomicReference<>();
+
+        PartitionConcurrentProcessor processor = mock(PartitionConcurrentProcessor.class);
+        when(processor.isRunning()).thenReturn(true);
+        doCallRealMethod().when(processor).process(anyList(), anyBoolean());
+        doAnswer(invocationOnMock -> {
+            offsetEvent.set(invocationOnMock.getArgument(0));
+            return null;
+        }).when(processor).generateWatermarkEvent(any());
+
+        // test null offset
+        offsetEvent.set(null);
+        TapdataHeartbeatEvent heartbeatEvent = new TapdataHeartbeatEvent();
+        processor.process(Collections.singletonList(heartbeatEvent), true);
+        assertNull(offsetEvent.get());
+
+        // test batchOffset
+        offsetEvent.set(null);
+        heartbeatEvent = Optional.of(new TapdataHeartbeatEvent()).map(event -> {
+            event.setBatchOffset(new BatchOffset(1, TableBatchReadStatus.RUNNING.name()));
+            return event;
+        }).get();
+        processor.process(Collections.singletonList(heartbeatEvent), true);
+        assertEquals(heartbeatEvent, offsetEvent.get());
+
+        // test steamOffset
+        offsetEvent.set(null);
+        heartbeatEvent = Optional.of(new TapdataHeartbeatEvent()).map(event -> {
+            event.setStreamOffset(System.currentTimeMillis());
+            return event;
+        }).get();
+        processor.process(Collections.singletonList(heartbeatEvent), true);
+        assertEquals(heartbeatEvent, offsetEvent.get());
+    }
+
     @Test
     @SneakyThrows
     void testProcessHeartBeat() {
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNodeTest.java
new file mode 100644
index 0000000000..8e1d7663a6
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNodeTest.java
@@ -0,0 +1,150 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.processor;
+
+import io.tapdata.entity.schema.value.DateTime;
+import io.tapdata.error.TaskDateProcessorExCode_17;
+import io.tapdata.exception.TapCodeException;
+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.springframework.test.util.ReflectionTestUtils;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-05-18 17:45
+ **/
+@DisplayName("Class HazelcastDateProcessorNode Test")
+class HazelcastDateProcessorNodeTest {
+
+	private HazelcastDateProcessorNode hazelcastDateProcessorNode;
+
+	@BeforeEach
+	void setUp() {
+		hazelcastDateProcessorNode = mock(HazelcastDateProcessorNode.class);
+	}
+
+	@Nested
+	@DisplayName("Method addTime test")
+	class addTimeTest {
+
+		private String tableName;
+
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastDateProcessorNode).addTime(any(), any(), any(), any(), any(), any());
+			tableName = "test";
+		}
+
+		@Test
+		@DisplayName("test add 8 hours")
+		void testAdd8Hours() {
+			ReflectionTestUtils.setField(hazelcastDateProcessorNode, "add", true);
+			ReflectionTestUtils.setField(hazelcastDateProcessorNode, "hours", 8);
+
+			List<String> addTimeFields = new ArrayList<>();
+			addTimeFields.add("_date");
+			Map<String, Object> after = new HashMap<>();
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 18, 23, 0, 0);
+			DateTime dateTime = new DateTime(localDateTime);
+			after.put("_date", dateTime);
+			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", dateTime);
+
+			Object result = after.get("_date");
+			assertInstanceOf(DateTime.class, result);
+			DateTime resultDateTime = (DateTime) result;
+			LocalDateTime resultLocalDateTime = LocalDateTime.ofInstant(resultDateTime.toInstant(), ZoneId.of("GMT"));
+			assertEquals(19, resultLocalDateTime.getDayOfMonth());
+			assertEquals(7, resultLocalDateTime.getHour());
+		}
+
+		@Test
+		@DisplayName("test minus 8 hours")
+		void testMinus8Hours() {
+			ReflectionTestUtils.setField(hazelcastDateProcessorNode, "add", false);
+			ReflectionTestUtils.setField(hazelcastDateProcessorNode, "hours", 8);
+
+			List<String> addTimeFields = new ArrayList<>();
+			addTimeFields.add("_date");
+			Map<String, Object> after = new HashMap<>();
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 18, 2, 0, 0);
+			DateTime dateTime = new DateTime(localDateTime);
+			after.put("_date", dateTime);
+			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", dateTime);
+
+			Object result = after.get("_date");
+			assertInstanceOf(DateTime.class, result);
+			DateTime resultDateTime = (DateTime) result;
+			LocalDateTime resultLocalDateTime = LocalDateTime.ofInstant(resultDateTime.toInstant(), ZoneId.of("GMT"));
+			assertEquals(17, resultLocalDateTime.getDayOfMonth());
+			assertEquals(18, resultLocalDateTime.getHour());
+		}
+
+		@Test
+		@DisplayName("test input a illegal DateTime")
+		void testIllegalDate() {
+			List<String> addTimeFields = new ArrayList<>();
+			addTimeFields.add("_date");
+			Map<String, Object> after = new HashMap<>();
+			DateTime dateTime = new DateTime("0000-0-0-0-0-0", DateTime.DATETIME_TYPE);
+			after.put("_date", dateTime);
+			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", dateTime);
+
+			Object result = after.get("_date");
+			assertSame(dateTime, result);
+		}
+
+		@Test
+		@DisplayName("test input parameter is not of the DateTime type, for example, an Instant type is used as input")
+		void testInputInstantType() {
+			List<String> addTimeFields = new ArrayList<>();
+			addTimeFields.add("_date");
+			Map<String, Object> after = new HashMap<>();
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 18, 2, 0, 0);
+			DateTime dateTime = new DateTime(localDateTime);
+			Instant instant = dateTime.toInstant();
+			after.put("_date", instant);
+			TapCodeException tapCodeException = assertThrows(TapCodeException.class,
+					() -> hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", instant)
+			);
+			assertEquals(TaskDateProcessorExCode_17.SELECTED_TYPE_IS_NON_TIME, tapCodeException.getCode());
+			System.out.println(tapCodeException.getMessage());
+		}
+
+		@Test
+		@DisplayName("test input null")
+		void testInputNull() {
+			List<String> addTimeFields = new ArrayList<>();
+			addTimeFields.add("_date");
+			Map<String, Object> after = new HashMap<>();
+			after.put("_date", null);
+			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", null);
+			assertNull(after.get("_date"));
+		}
+
+		@Test
+		@DisplayName("test parameter k is not included addTimeFields list")
+		void testNotContainsAddTimeFields() {
+			List<String> addTimeFields = new ArrayList<>();
+			Map<String, Object> after = new HashMap<>();
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 18, 2, 0, 0);
+			DateTime dateTime = new DateTime(localDateTime);
+			after.put("_date", dateTime);
+			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", dateTime);
+			assertSame(dateTime, after.get("_date"));
+		}
+	}
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNodeTest.java
new file mode 100644
index 0000000000..7bea976be5
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNodeTest.java
@@ -0,0 +1,56 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.processor;
+
+import io.tapdata.entity.codec.TapCodecsRegistry;
+import io.tapdata.entity.codec.ToTapValueCodec;
+import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
+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.springframework.test.util.ReflectionTestUtils;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-05-18 17:22
+ **/
+@DisplayName("Class HazelcastProcessorBaseNode Test")
+class HazelcastProcessorBaseNodeTest {
+
+	private HazelcastProcessorBaseNode hazelcastProcessorBaseNode;
+
+	@BeforeEach
+	void setUp() {
+		hazelcastProcessorBaseNode = mock(HazelcastProcessorBaseNode.class);
+	}
+
+	@Nested
+	@DisplayName("Method initFilterCodec test")
+	class initFilterCodecTest {
+		@BeforeEach
+		void setUp() {
+			when(hazelcastProcessorBaseNode.initFilterCodec()).thenCallRealMethod();
+		}
+
+		@Test
+		@DisplayName("test main process")
+		void testMainProcess() {
+			TapCodecsFilterManager tapCodecsFilterManager = hazelcastProcessorBaseNode.initFilterCodec();
+			assertNotNull(tapCodecsFilterManager);
+			TapCodecsRegistry codecsRegistry = tapCodecsFilterManager.getCodecsRegistry();
+			assertNotNull(codecsRegistry);
+			Object classFromTapValueCodecMap = ReflectionTestUtils.getField(codecsRegistry, "classFromTapValueCodecMap");
+			assertInstanceOf(ConcurrentHashMap.class, classFromTapValueCodecMap);
+			assertTrue(((ConcurrentHashMap<?, ?>) classFromTapValueCodecMap).isEmpty());
+			ToTapValueCodec<?> customToTapValueCodec = codecsRegistry.getCustomToTapValueCodec(byte[].class);
+			assertNotNull(customToTapValueCodec);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/util/ProcessNodeSchemaUtilTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/util/ProcessNodeSchemaUtilTest.java
new file mode 100644
index 0000000000..81011d6bf4
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/util/ProcessNodeSchemaUtilTest.java
@@ -0,0 +1,327 @@
+package io.tapdata.flow.engine.util;
+
+import io.tapdata.entity.schema.TapField;
+import io.tapdata.entity.schema.TapTable;
+import io.tapdata.entity.schema.type.TapArray;
+import io.tapdata.entity.schema.type.TapMap;
+import io.tapdata.entity.schema.type.TapString;
+import io.tapdata.entity.schema.value.TapArrayValue;
+import io.tapdata.entity.schema.value.TapMapValue;
+import io.tapdata.entity.schema.value.TapStringValue;
+import io.tapdata.entity.simplify.TapSimplify;
+import io.tapdata.entity.utils.JavaTypesToTapTypes;
+import io.tapdata.observable.logging.ObsLogger;
+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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static io.tapdata.entity.simplify.TapSimplify.tapArray;
+import static io.tapdata.entity.simplify.TapSimplify.tapMap;
+import static io.tapdata.entity.simplify.TapSimplify.tapString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ProcessNodeSchemaUtilTest {
+
+
+    @Nested
+    @DisplayName("Test preserving sub attributes after model inference on Js nodes")
+    class RetainedOldSubFields{
+        TapTable tapTable;
+        LinkedHashMap<String, TapField> oldNameFieldMap;
+        TapField f;
+        LinkedHashMap<String, TapField> nameFieldMap;
+        TapField field;
+        Map<String, Object> afterValue;
+        @BeforeEach
+        void init() {
+
+            f = mock(TapField.class);
+            field = mock(TapField.class);
+
+            tapTable = new TapTable("id");
+            nameFieldMap = new LinkedHashMap<>();
+            nameFieldMap.put("f", f);
+            when(f.getTapType()).thenReturn(new TapMap());
+            tapTable.setNameFieldMap(nameFieldMap);
+
+            oldNameFieldMap = new LinkedHashMap<>();
+            oldNameFieldMap.put("f", field);
+            when(field.getTapType()).thenReturn(new TapMap());
+            afterValue = new HashMap<>();
+
+        }
+        @Test
+        void testNormal() {
+            Assertions.assertDoesNotThrow(() -> ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, afterValue));
+            Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+            Assertions.assertNotNull(tapTable.getNameFieldMap().get("f"));
+        }
+
+        @Test
+        void testOldNameFieldMapIsEmpty() {
+            oldNameFieldMap.clear();
+            Assertions.assertDoesNotThrow(() -> ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, afterValue));
+            Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+            Assertions.assertNotNull(tapTable.getNameFieldMap().get("f"));
+        }
+
+        @Test
+        void testContainsSubFieldButNotContainsSubField() {
+            TapField subField = mock(TapField.class);
+            when(subField.getName()).thenReturn("f.id");
+            oldNameFieldMap.put("f.id", subField);
+            afterValue.put("f", new HashMap<>());
+            Assertions.assertDoesNotThrow(() -> ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, afterValue));
+            Assertions.assertEquals(2, tapTable.getNameFieldMap().size());
+            Assertions.assertNotNull(tapTable.getNameFieldMap().get("f.id"));
+        }
+        @Test
+        void testContainsSubFieldAndContainsSubField() {
+            TapField subField = mock(TapField.class);
+            when(subField.getName()).thenReturn("f.id");
+            oldNameFieldMap.put("f.id", subField);
+            afterValue.put("f", new HashMap<>());
+            afterValue.put("f.id", "id");
+            Assertions.assertDoesNotThrow(() -> ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, afterValue));
+            Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+            Assertions.assertNull(tapTable.getNameFieldMap().get("f.id"));
+        }
+
+        @Test
+        void testNotContainsSubFieldButNotContainsSubField() {
+            TapField subField = mock(TapField.class);
+            when(subField.getName()).thenReturn("f.id");
+            oldNameFieldMap.put("f.id", subField);
+            Assertions.assertDoesNotThrow(() -> ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, afterValue));
+            Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+            Assertions.assertNull(tapTable.getNameFieldMap().get("f.id"));
+        }
+        @Test
+        void testOldNameFieldMapNotContainsFatherField() {
+            TapField subField = mock(TapField.class);
+            when(subField.getName()).thenReturn("f.id");
+            oldNameFieldMap.put("f.id", subField);
+            oldNameFieldMap.remove("f");
+            afterValue.put("f", new HashMap<>());
+            Assertions.assertDoesNotThrow(() -> ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, afterValue));
+            Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+            Assertions.assertNull(tapTable.getNameFieldMap().get("f.id"));
+        }
+        @Test
+        void testTapTableNotContainsFatherField() {
+            TapField subField = mock(TapField.class);
+            when(subField.getName()).thenReturn("f.id");
+            nameFieldMap.remove("f");
+            oldNameFieldMap.put("f.id", subField);
+            afterValue.put("f", new HashMap<>());
+            Assertions.assertDoesNotThrow(() -> ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, afterValue));
+            Assertions.assertEquals(0, tapTable.getNameFieldMap().size());
+            Assertions.assertNull(tapTable.getNameFieldMap().get("f.id"));
+        }
+        @Test
+        void testTapTableContainsFatherFieldButFieldTapTypeIsNull() {
+            TapField subField = mock(TapField.class);
+            when(subField.getName()).thenReturn("f.id");
+            when(f.getTapType()).thenReturn(null);
+            oldNameFieldMap.put("f.id", subField);
+            afterValue.put("f", new HashMap<>());
+            Assertions.assertDoesNotThrow(() -> ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, afterValue));
+            Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+            Assertions.assertNull(tapTable.getNameFieldMap().get("f.id"));
+        }
+        @Test
+        void testBeforeTapNotContainsAfterTap() {
+            TapField subField = mock(TapField.class);
+            when(subField.getName()).thenReturn("f.id");
+            when(f.getTapType()).thenReturn(new TapArray());
+            oldNameFieldMap.put("f.id", subField);
+            afterValue.put("f", new HashMap<>());
+            Assertions.assertDoesNotThrow(() -> ProcessNodeSchemaUtil.retainedOldSubFields(tapTable, oldNameFieldMap, afterValue));
+            Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+            Assertions.assertNull(tapTable.getNameFieldMap().get("f.id"));
+        }
+    }
+
+    @Nested
+    @DisplayName("")
+    class ScanTapFieldTest {
+        ObsLogger obsLogger;
+        TapTable tapTable;
+        Map<String, TapField> oldNameFieldMap;
+        @BeforeEach
+        void init() {
+            tapTable = new TapTable();
+            obsLogger = mock(ObsLogger.class);
+            when(obsLogger.isDebugEnabled()).thenReturn(true);
+            doNothing().when(obsLogger).debug(anyString(), anyString(), any(Class.class));
+            doNothing().when(obsLogger).debug(anyString(), anyString(), anyString());
+            oldNameFieldMap = new LinkedHashMap<>();
+        }
+        @Nested
+        class NormalFieldTest {
+            @Test
+            void testNormalField() {
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", "value", obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(1)).debug(anyString(), anyString(), any(Class.class));
+            }
+
+            @Test
+            void testNotSupportDebug() {
+                when(obsLogger.isDebugEnabled()).thenReturn(false);
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", "value", obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(0)).debug(anyString(), anyString(), any(Class.class));
+            }
+            @Test
+            void testTapRow() {
+                when(obsLogger.isDebugEnabled()).thenReturn(false);
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", null, obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(0)).debug(anyString(), anyString(), any(Class.class));
+            }
+
+            @Test
+            void testOldNameFieldMapIsEmpty() {
+                ProcessNodeSchemaUtil.scanTapField(tapTable, null, "key", "value", obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(1)).debug(anyString(), anyString(), any(Class.class));
+            }
+
+            @Test
+            void testOldNameFieldMapContainsField() {
+                oldNameFieldMap.put("key", new TapField().name("key").tapType(JavaTypesToTapTypes.toTapType(JavaTypesToTapTypes.JAVA_String)));
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", "value", obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(1)).debug(anyString(), anyString(), any(Class.class));
+            }
+            @Test
+            void testOldNameFieldMapContainsFieldButOldTapTypeIsNull() {
+                oldNameFieldMap.put("key", new TapField().name("key"));
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", "value", obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(1)).debug(anyString(), anyString(), any(Class.class));
+            }
+            @Test
+            void testOldNameFieldMapContainsFieldButOldTapTypeNotEqualsTapType() {
+                oldNameFieldMap.put("key", new TapField().name("key").tapType(JavaTypesToTapTypes.toTapType(JavaTypesToTapTypes.JAVA_Double)));
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", "value", obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(1)).debug(anyString(), anyString(), any(Class.class));
+            }
+            @Test
+            void testOldNameFieldMapContainsFieldButTapTypeIsTapRow() {
+                oldNameFieldMap.put("key", new TapField().name("key").tapType(JavaTypesToTapTypes.toTapType(JavaTypesToTapTypes.JAVA_Double)));
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", null, obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(1)).debug(anyString(), anyString(), anyString());
+            }
+            @Test
+            void testTapValue() {
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", new TapStringValue("value").tapType(tapString().bytes(1024L)), obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(1)).debug(anyString(), anyString(), any(Class.class));
+            }
+        }
+
+        @Nested
+        class MapFieldTest {
+            @Test
+            void testNormalMapField() {
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", new HashMap<>(), obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(1)).debug(anyString(), anyString(), any(Class.class));
+            }
+
+            @Test
+            void testTapMap() {
+                Map<String, Object> map = new HashMap<>();
+                map.put("key", "sub");
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", map, obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(2, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key.key"));
+                verify(obsLogger, times(2)).debug(anyString(), anyString(), any(Class.class));
+            }
+            @Test
+            void testTapMapValue() {
+                TapMapValue tapMapValue = new TapMapValue();
+                Map<String, Object> map = new HashMap<>();
+                map.put("key", "sub");
+                tapMapValue.setValue(map);
+                tapMapValue.setTapType(tapMap());
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", tapMapValue, obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(2, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key.key"));
+                verify(obsLogger, times(2)).debug(anyString(), anyString(), any(Class.class));
+            }
+        }
+
+        @Nested
+        class ArrayTest {
+            @Test
+            void testNormalMapField() {
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", new ArrayList<>(), obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(1, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                verify(obsLogger, times(1)).debug(anyString(), anyString(), any(Class.class));
+            }
+
+            @Test
+            void testTapArray() {
+                List<Object> list = new ArrayList<>();
+                list.add("1");
+                Map<String, Object> map = new HashMap<>();
+                map.put("key", "sub");
+                list.add(map);
+                TapArrayValue tapArrayValue = new TapArrayValue();
+                tapArrayValue.setValue(list);
+                tapArrayValue.setTapType(tapArray());
+                ProcessNodeSchemaUtil.scanTapField(tapTable, oldNameFieldMap, "key", tapArrayValue, obsLogger);
+                Assertions.assertNotNull(tapTable.getNameFieldMap());
+                Assertions.assertEquals(2, tapTable.getNameFieldMap().size());
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key"));
+                Assertions.assertNotNull(tapTable.getNameFieldMap().get("key.key"));
+                verify(obsLogger, times(2)).debug(anyString(), anyString(), any(Class.class));
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/iengine/iengine-common/src/test/java/io/tapdata/flow/engine/V2/util/TapCodecUtilTest.java b/iengine/iengine-common/src/test/java/io/tapdata/flow/engine/V2/util/TapCodecUtilTest.java
new file mode 100644
index 0000000000..024e9e77eb
--- /dev/null
+++ b/iengine/iengine-common/src/test/java/io/tapdata/flow/engine/V2/util/TapCodecUtilTest.java
@@ -0,0 +1,254 @@
+package io.tapdata.flow.engine.V2.util;
+
+import io.tapdata.entity.codec.FromTapValueCodec;
+import io.tapdata.entity.codec.TapCodecsRegistry;
+import io.tapdata.entity.codec.ToTapValueCodec;
+import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
+import io.tapdata.entity.schema.type.TapBinary;
+import io.tapdata.entity.schema.type.TapDate;
+import io.tapdata.entity.schema.type.TapDateTime;
+import io.tapdata.entity.schema.type.TapYear;
+import io.tapdata.entity.schema.value.*;
+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 java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-05-16 14:10
+ **/
+@DisplayName("Class TapCodecUtil Test")
+class TapCodecUtilTest {
+
+	@Nested
+	@DisplayName("Method addTag test")
+	class addTagTest {
+		@Test
+		@DisplayName("Test main process")
+		void testMainProcess() {
+			String value = "test";
+			byte[] result = TapCodecUtil.addTag(value.getBytes(StandardCharsets.UTF_8), TapCodecUtil.TAP_YEAR_BYTE_TAG_V2);
+			assertEquals("TapCodecUtil\u0001test", new String(result, StandardCharsets.UTF_8));
+		}
+
+		@Test
+		@DisplayName("Test input null")
+		void testInputNull() {
+			byte[] result = assertDoesNotThrow(() -> TapCodecUtil.addTag(null, TapCodecUtil.TAP_YEAR_BYTE_TAG_V2));
+			assertNull(result);
+		}
+	}
+
+	@Test
+	@DisplayName("Method createEngineCodecsFilterManger test")
+	void testCreateEngineCodecsFilterManger() {
+		TapCodecsFilterManager engineCodecsFilterManger = TapCodecUtil.createEngineCodecsFilterManger();
+		assertNotNull(engineCodecsFilterManger);
+		TapCodecsRegistry codecsRegistry = engineCodecsFilterManger.getCodecsRegistry();
+		assertNotNull(codecsRegistry);
+		assertNotNull(codecsRegistry.getCustomFromTapValueCodec(TapDateValue.class));
+		assertNotNull(codecsRegistry.getCustomFromTapValueCodec(TapDateTimeValue.class));
+		assertNotNull(codecsRegistry.getCustomFromTapValueCodec(TapYearValue.class));
+		assertNotNull(codecsRegistry.getCustomFromTapValueCodec(TapTimeValue.class));
+		assertNotNull(codecsRegistry.getCustomToTapValueCodec(byte[].class));
+	}
+
+	@Nested
+	@DisplayName("Test from and to TapValue v2")
+	class fromAndToTapValueV2Test {
+
+		private TapCodecsFilterManager engineCodecsFilterManger;
+
+		@BeforeEach
+		void setUp() {
+			engineCodecsFilterManger = TapCodecUtil.createEngineCodecsFilterManger();
+		}
+
+		@Test
+		@DisplayName("test from and to TapDateValue")
+		void testFromAndToTapDateValue() {
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 16, 0, 0);
+			DateTime dateTime = new DateTime(localDateTime);
+			TapDateValue tapDateValue = new TapDateValue(dateTime);
+			FromTapValueCodec<TapDateValue> customFromTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomFromTapValueCodec(TapDateValue.class);
+			Object result = customFromTapValueCodec.fromTapValue(tapDateValue);
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(result.getClass());
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(result, new TapDate());
+			assertInstanceOf(TapDateValue.class, tapValue);
+			assertNotSame(tapDateValue, tapValue);
+			assertEquals(localDateTime.toEpochSecond(ZoneOffset.UTC), ((TapDateValue) tapValue).getValue().getSeconds());
+		}
+
+		@Test
+		@DisplayName("test from and to illegal TapDateValue")
+		void testFromAndToIllegalTapDateValue() {
+			String illegalDate = "0001-00-00";
+			DateTime dateTime = new DateTime(illegalDate, DateTime.DATE_TYPE);
+			TapDateValue tapDateValue = new TapDateValue(dateTime);
+			FromTapValueCodec<TapDateValue> customFromTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomFromTapValueCodec(TapDateValue.class);
+			Object result = customFromTapValueCodec.fromTapValue(tapDateValue);
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(result.getClass());
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(result, new TapDate());
+			assertInstanceOf(TapDateValue.class, tapValue);
+			assertNotSame(tapDateValue, tapValue);
+			assertTrue(((TapDateValue) tapValue).getValue().isContainsIllegal());
+			assertEquals(illegalDate, ((TapDateValue) tapValue).getValue().getIllegalDate());
+		}
+
+		@Test
+		@DisplayName("test from and to TapDateTimeValue")
+		void testFromAndToTapDateTimeValue() {
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 16, 17, 5, 12, 123);
+			DateTime dateTime = new DateTime(localDateTime);
+			TapDateTimeValue tapDateTimeValue = new TapDateTimeValue(dateTime);
+			FromTapValueCodec<TapDateTimeValue> customFromTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomFromTapValueCodec(TapDateTimeValue.class);
+			Object result = customFromTapValueCodec.fromTapValue(tapDateTimeValue);
+			assertInstanceOf(Instant.class, result);
+			assertEquals(localDateTime.toEpochSecond(ZoneOffset.UTC), ((Instant) result).getEpochSecond());
+			assertEquals(localDateTime.getNano(), ((Instant) result).getNano());
+		}
+
+		@Test
+		@DisplayName("test from and to illegal TapDateTimeValue")
+		void testFromAndToIllegalTapDatetimeValue() {
+			String illegalDatetime = "0001-0-01-12-01-02";
+			String expect = "0001-00-01 12:01:02";
+			DateTime dateTime = new DateTime(illegalDatetime, DateTime.DATETIME_TYPE);
+			TapDateTimeValue tapDateTimeValue = new TapDateTimeValue(dateTime);
+			FromTapValueCodec<TapDateTimeValue> customFromTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomFromTapValueCodec(TapDateTimeValue.class);
+			Object result = customFromTapValueCodec.fromTapValue(tapDateTimeValue);
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(result.getClass());
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(result, new TapDateTime());
+			assertInstanceOf(TapDateTimeValue.class, tapValue);
+			assertTrue(((TapDateTimeValue) tapValue).getValue().isContainsIllegal());
+			assertEquals(expect, ((TapDateTimeValue) tapValue).getValue().getIllegalDate());
+		}
+
+		@Test
+		@DisplayName("test from and to TapYearValue")
+		void testFromAndToTapYearValue() {
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 1, 1, 0, 0);
+			DateTime dateTime = new DateTime(localDateTime);
+			TapYearValue tapYearValue = new TapYearValue(dateTime);
+			FromTapValueCodec<TapYearValue> customFromTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomFromTapValueCodec(TapYearValue.class);
+			Object result = customFromTapValueCodec.fromTapValue(tapYearValue);
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(result.getClass());
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(result, new TapYear());
+			assertInstanceOf(TapYearValue.class, tapValue);
+			assertEquals(localDateTime.toEpochSecond(ZoneOffset.UTC), ((TapYearValue) tapValue).getValue().getSeconds());
+		}
+
+		@Test
+		@DisplayName("test from and to TapTimeValue")
+		void testFromAndToTapTimeValue() {
+			LocalDateTime localDateTime = LocalDateTime.of(1970, 1, 1, 17, 18, 47);
+			DateTime dateTime = new DateTime(localDateTime);
+			TapTimeValue tapTimeValue = new TapTimeValue(dateTime);
+			FromTapValueCodec<TapTimeValue> customFromTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomFromTapValueCodec(TapTimeValue.class);
+			Object result = customFromTapValueCodec.fromTapValue(tapTimeValue);
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(result.getClass());
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(result, new TapYear());
+			assertInstanceOf(TapTimeValue.class, tapValue);
+			assertEquals(tapTimeValue.getValue().toTimeStr(), ((TapTimeValue) tapValue).getValue().toTimeStr());
+		}
+
+		@Test
+		@DisplayName("test input null")
+		void testInputNull() {
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(byte[].class);
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(null, new TapDateTime());
+			assertInstanceOf(TapBinaryValue.class, tapValue);
+			assertNull(((TapBinaryValue) tapValue).getValue());
+		}
+
+		@Test
+		@DisplayName("test input other normal bytes value")
+		void testInputNormalBytes() {
+			String str = "test";
+			byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(byte[].class);
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(bytes, new TapBinary());
+			assertInstanceOf(TapBinaryValue.class, tapValue);
+			assertArrayEquals(bytes, ((TapBinaryValue) tapValue).getValue());
+
+			str = "testtesttesttesttesttest";
+			bytes = str.getBytes(StandardCharsets.UTF_8);
+			tapValue = customToTapValueCodec.toTapValue(bytes, new TapBinary());
+			assertInstanceOf(TapBinaryValue.class, tapValue);
+			assertArrayEquals(bytes, ((TapBinaryValue) tapValue).getValue());
+		}
+	}
+
+	@Nested
+	@DisplayName("Test from and to TapValue v1")
+	class fromAndToTapValueV1 {
+
+		private TapCodecsFilterManager engineCodecsFilterManger;
+
+		@BeforeEach
+		void setUp() {
+			engineCodecsFilterManger = TapCodecUtil.createEngineCodecsFilterManger();
+		}
+
+		@Test
+		@DisplayName("test from and to TapDateValue")
+		void testFromAndToTapDateValue() {
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 16, 0, 0);
+			DateTime dateTime = new DateTime(localDateTime);
+			TapDateValue tapDateValue = new TapDateValue(dateTime);
+			Object result = TapCodecUtil.fromTapValueV1(tapDateValue);
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(result.getClass());
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(result, new TapDate());
+			assertInstanceOf(TapDateValue.class, tapValue);
+			assertNotSame(tapDateValue, tapValue);
+			assertEquals(localDateTime.toEpochSecond(ZoneOffset.ofHours(8)), ((TapDateValue) tapValue).getValue().getSeconds());
+		}
+
+		@Test
+		@DisplayName("test from and to TapDateTimeValue")
+		void testFromAndToTapDateTimeValue() {
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 16, 17, 5, 12, 123);
+			DateTime dateTime = new DateTime(localDateTime);
+			TapDateTimeValue tapDateTimeValue = new TapDateTimeValue(dateTime);
+			Object result = TapCodecUtil.fromTapValueV1(tapDateTimeValue);
+			assertInstanceOf(Instant.class, result);
+			assertEquals(localDateTime.toEpochSecond(ZoneOffset.UTC), ((Instant) result).getEpochSecond());
+			assertEquals(localDateTime.getNano(), ((Instant) result).getNano());
+		}
+
+		@Test
+		@DisplayName("test from and to TapYearValue")
+		void testFromAndToTapYearValue() {
+			LocalDateTime localDateTime = LocalDateTime.of(2024, 1, 1, 0, 0);
+			DateTime dateTime = new DateTime(localDateTime);
+			TapYearValue tapYearValue = new TapYearValue(dateTime);
+			Object result = TapCodecUtil.fromTapValueV1(tapYearValue);
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(result.getClass());
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(result, new TapYear());
+			assertInstanceOf(TapYearValue.class, tapValue);
+			assertEquals(localDateTime.toEpochSecond(ZoneOffset.ofHours(8)), ((TapYearValue) tapValue).getValue().getSeconds());
+		}
+
+		@Test
+		@DisplayName("test from and to TapTimeValue")
+		void testFromAndToTapTimeValue() {
+			LocalDateTime localDateTime = LocalDateTime.of(1970, 1, 1, 17, 18, 47);
+			DateTime dateTime = new DateTime(localDateTime);
+			TapTimeValue tapTimeValue = new TapTimeValue(dateTime);
+			Object result = TapCodecUtil.fromTapValueV1(tapTimeValue);
+			ToTapValueCodec<?> customToTapValueCodec = engineCodecsFilterManger.getCodecsRegistry().getCustomToTapValueCodec(result.getClass());
+			TapValue<?, ?> tapValue = customToTapValueCodec.toTapValue(result, new TapYear());
+			assertInstanceOf(TapTimeValue.class, tapValue);
+			assertEquals(tapTimeValue.getValue().toTimeStr(), ((TapTimeValue) tapValue).getValue().toTimeStr());
+		}
+	}
+}
\ No newline at end of file
diff --git a/iengine/iengine-common/src/test/java/io/tapdata/flow/engine/V2/util/TapEventUtilTest.java b/iengine/iengine-common/src/test/java/io/tapdata/flow/engine/V2/util/TapEventUtilTest.java
index 596b008b52..5e5b66a835 100644
--- a/iengine/iengine-common/src/test/java/io/tapdata/flow/engine/V2/util/TapEventUtilTest.java
+++ b/iengine/iengine-common/src/test/java/io/tapdata/flow/engine/V2/util/TapEventUtilTest.java
@@ -1,15 +1,21 @@
 package io.tapdata.flow.engine.V2.util;
 
+import com.tapdata.entity.TapdataShareLogEvent;
 import io.tapdata.entity.event.TapBaseEvent;
 import io.tapdata.entity.event.TapEvent;
+import io.tapdata.entity.event.control.HeartbeatEvent;
+import io.tapdata.entity.event.ddl.table.TapAlterFieldNameEvent;
+import io.tapdata.entity.event.dml.TapDeleteRecordEvent;
 import io.tapdata.entity.event.dml.TapInsertRecordEvent;
 import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import java.util.ArrayList;
 import java.util.List;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -81,4 +87,173 @@ void test3(){
 		Boolean isReplaceEvent = TapEventUtil.getIsReplaceEvent(tapInsertRecordEvent);
 		assertEquals(Boolean.FALSE,isReplaceEvent);
 	}
+	@Nested
+	class getIllegalField{
+		@Test
+		@DisplayName("test getIllegalField method for TapInsertRecordEvent")
+		void test1(){
+			TapEvent tapEvent = new TapInsertRecordEvent();
+			List mock = mock(List.class);
+			((TapInsertRecordEvent)tapEvent).setAfterIllegalDateFieldName(mock);
+			Map<String, List<String>> actual = TapEventUtil.getIllegalField(tapEvent);
+			assertEquals(mock, actual.get("after"));
+		}
+		@Test
+		@DisplayName("test getIllegalField method for TapUpdateRecordEvent")
+		void test2(){
+			TapEvent tapEvent = new TapUpdateRecordEvent();
+			List before = mock(List.class);
+			List after = mock(List.class);
+			((TapUpdateRecordEvent)tapEvent).setBeforeIllegalDateFieldName(before);
+			((TapUpdateRecordEvent)tapEvent).setAfterIllegalDateFieldName(after);
+			Map<String, List<String>> actual = TapEventUtil.getIllegalField(tapEvent);
+			assertEquals(before, actual.get("before"));
+			assertEquals(after, actual.get("after"));
+		}
+		@Test
+		@DisplayName("test getIllegalField method for TapDeleteRecordEvent")
+		void test3(){
+			TapEvent tapEvent = new TapDeleteRecordEvent();
+			List mock = mock(List.class);
+			((TapDeleteRecordEvent)tapEvent).setBeforeIllegalDateFieldName(mock);
+			Map<String, List<String>> actual = TapEventUtil.getIllegalField(tapEvent);
+			assertEquals(mock, actual.get("before"));
+		}
+		@Test
+		@DisplayName("test getIllegalField method for other event")
+		void test4(){
+			TapEvent tapEvent = new HeartbeatEvent();
+			Map<String, List<String>> actual = TapEventUtil.getIllegalField(tapEvent);
+			assertNull(actual);
+		}
+	}
+	@Nested
+	class setContainsIllegalDate{
+		private TapEvent event;
+		private boolean containsIllegalDate;
+		@Test
+		@DisplayName("test setContainsIllegalDate method for TapRecordEvent")
+		void test1(){
+			event = new TapInsertRecordEvent();
+			containsIllegalDate = true;
+			TapEventUtil.setContainsIllegalDate(event,containsIllegalDate);
+			assertEquals(containsIllegalDate, ((TapInsertRecordEvent)event).getContainsIllegalDate());
+		}
+		@Test
+		@DisplayName("test setContainsIllegalDate method for TapAlterFieldNameEvent")
+		void test2(){
+			event = new TapAlterFieldNameEvent();
+			containsIllegalDate = true;
+			TapEventUtil.setContainsIllegalDate(event,containsIllegalDate);
+			assertNotNull(event);
+		}
+	}
+	@Nested
+	class addBeforeIllegalDateField{
+		private TapEvent tapEvent;
+		private String fieldName;
+		@Test
+		@DisplayName("test addBeforeIllegalDateField method for TapUpdateRecordEvent")
+		void test1(){
+			tapEvent = new TapUpdateRecordEvent();
+			fieldName = "test_field";
+			TapEventUtil.addBeforeIllegalDateField(tapEvent,fieldName);
+			assertEquals(fieldName, ((TapUpdateRecordEvent)tapEvent).getBeforeIllegalDateFieldName().get(0));
+			assertNull(((TapUpdateRecordEvent)tapEvent).getAfterIllegalDateFieldName());
+		}
+		@Test
+		@DisplayName("test addBeforeIllegalDateField method for TapUpdateRecordEvent when fieldNameList is not null")
+		void test2(){
+			tapEvent = new TapUpdateRecordEvent();
+			List<String> fieldNameList = new ArrayList<>();
+			fieldNameList.add("field");
+			((TapUpdateRecordEvent)tapEvent).setBeforeIllegalDateFieldName(fieldNameList);
+			fieldName = "test_field";
+			TapEventUtil.addBeforeIllegalDateField(tapEvent,fieldName);
+			assertEquals(fieldNameList, ((TapUpdateRecordEvent)tapEvent).getBeforeIllegalDateFieldName());
+			assertEquals(2, ((TapUpdateRecordEvent)tapEvent).getBeforeIllegalDateFieldName().size());
+			assertNull(((TapUpdateRecordEvent)tapEvent).getAfterIllegalDateFieldName());
+		}
+		@Test
+		@DisplayName("test addBeforeIllegalDateField method for TapDeleteRecordEvent")
+		void test3(){
+			tapEvent = new TapDeleteRecordEvent();
+			fieldName = "test_field";
+			TapEventUtil.addBeforeIllegalDateField(tapEvent,fieldName);
+			assertEquals(fieldName, ((TapDeleteRecordEvent)tapEvent).getBeforeIllegalDateFieldName().get(0));
+		}
+		@Test
+		@DisplayName("test addBeforeIllegalDateField method for TapDeleteRecordEvent when fieldNameList is not null")
+		void test4(){
+			tapEvent = new TapDeleteRecordEvent();
+			List<String> fieldNameList = new ArrayList<>();
+			fieldNameList.add("field");
+			((TapDeleteRecordEvent)tapEvent).setBeforeIllegalDateFieldName(fieldNameList);
+			fieldName = "test_field";
+			TapEventUtil.addBeforeIllegalDateField(tapEvent,fieldName);
+			assertEquals(fieldNameList, ((TapDeleteRecordEvent)tapEvent).getBeforeIllegalDateFieldName());
+			assertEquals(2, ((TapDeleteRecordEvent)tapEvent).getBeforeIllegalDateFieldName().size());
+		}
+		@Test
+		@DisplayName("test addBeforeIllegalDateField method for TapInsertRecordEvent")
+		void test5(){
+			tapEvent = new TapInsertRecordEvent();
+			TapEventUtil.addBeforeIllegalDateField(tapEvent, "test_field");
+			assertNotNull(tapEvent);
+		}
+	}
+	@Nested
+	class addAfterIllegalDateField{
+		private TapEvent tapEvent;
+		private String fieldName;
+		@Test
+		@DisplayName("test addAfterIllegalDateField method for TapInsertRecordEvent")
+		void test1(){
+			tapEvent = new TapInsertRecordEvent();
+			fieldName = "test_field";
+			TapEventUtil.addAfterIllegalDateField(tapEvent,fieldName);
+			assertEquals(fieldName, ((TapInsertRecordEvent)tapEvent).getAfterIllegalDateFieldName().get(0));
+		}
+		@Test
+		@DisplayName("test addBeforeIllegalDateField method for TapInsertRecordEvent when fieldNameList is not null")
+		void test2(){
+			tapEvent = new TapInsertRecordEvent();
+			List<String> fieldNameList = new ArrayList<>();
+			fieldNameList.add("field");
+			((TapInsertRecordEvent)tapEvent).setAfterIllegalDateFieldName(fieldNameList);
+			fieldName = "test_field";
+			TapEventUtil.addAfterIllegalDateField(tapEvent,fieldName);
+			assertEquals(fieldNameList, ((TapInsertRecordEvent)tapEvent).getAfterIllegalDateFieldName());
+			assertEquals(2, ((TapInsertRecordEvent)tapEvent).getAfterIllegalDateFieldName().size());
+		}
+		@Test
+		@DisplayName("test addBeforeIllegalDateField method for TapUpdateRecordEvent")
+		void test3(){
+			tapEvent = new TapUpdateRecordEvent();
+			fieldName = "test_field";
+			TapEventUtil.addAfterIllegalDateField(tapEvent,fieldName);
+			assertEquals(fieldName, ((TapUpdateRecordEvent)tapEvent).getAfterIllegalDateFieldName().get(0));
+			assertNull(((TapUpdateRecordEvent)tapEvent).getBeforeIllegalDateFieldName());
+		}
+		@Test
+		@DisplayName("test addBeforeIllegalDateField method for TapUpdateRecordEvent when fieldNameList is not null")
+		void test4(){
+			tapEvent = new TapUpdateRecordEvent();
+			List<String> fieldNameList = new ArrayList<>();
+			fieldNameList.add("field");
+			((TapUpdateRecordEvent)tapEvent).setAfterIllegalDateFieldName(fieldNameList);
+			fieldName = "test_field";
+			TapEventUtil.addAfterIllegalDateField(tapEvent,fieldName);
+			assertEquals(fieldNameList, ((TapUpdateRecordEvent)tapEvent).getAfterIllegalDateFieldName());
+			assertEquals(2, ((TapUpdateRecordEvent)tapEvent).getAfterIllegalDateFieldName().size());
+			assertNull(((TapUpdateRecordEvent)tapEvent).getBeforeIllegalDateFieldName());
+		}
+		@Test
+		@DisplayName("test addBeforeIllegalDateField method for TapDeleteRecordEvent")
+		void test5(){
+			tapEvent = new TapDeleteRecordEvent();
+			TapEventUtil.addAfterIllegalDateField(tapEvent, "test_field");
+			assertNotNull(tapEvent);
+		}
+	}
 }
diff --git a/iengine/modules/observable-module/src/test/java/io/tapdata/observable/metric/handler/HandlerUtilTest.java b/iengine/modules/observable-module/src/test/java/io/tapdata/observable/metric/handler/HandlerUtilTest.java
index 5c00446ec0..2a7014e4aa 100644
--- a/iengine/modules/observable-module/src/test/java/io/tapdata/observable/metric/handler/HandlerUtilTest.java
+++ b/iengine/modules/observable-module/src/test/java/io/tapdata/observable/metric/handler/HandlerUtilTest.java
@@ -583,8 +583,8 @@ public void testGetTotal() {
         class IncrProcessTimeTotalTest {
             @Test
             public void testIncrProcessTimeTotal() {
-                Long now = System.currentTimeMillis();
-                Long time = System.currentTimeMillis() - 10;
+                long now = System.currentTimeMillis();
+                long time = now - 10;
                 recorder.incrProcessTimeTotal(now, time);
                 Assertions.assertNotNull(recorder.getProcessTimeTotal());
                 Assertions.assertEquals(new Long(now - time), recorder.getProcessTimeTotal());
@@ -599,8 +599,8 @@ public void testIncrProcessTimeTotal0() {
             @Test
             public void testIncrProcessTimeTotal1() {
                 recorder.setProcessTimeTotal(null);
-                Long now = System.currentTimeMillis();
-                Long time = System.currentTimeMillis() - 10;
+                long now = System.currentTimeMillis();
+                long time = now - 10;
                 recorder.incrProcessTimeTotal(now, time);
                 Assertions.assertNotNull(recorder.getProcessTimeTotal());
                 Assertions.assertEquals(new Long(now - time), recorder.getProcessTimeTotal());
@@ -609,8 +609,8 @@ public void testIncrProcessTimeTotal1() {
             @Test
             public void testIncrProcessTimeTotal2() {
                 recorder.setProcessTimeTotal(0L);
-                Long now = System.currentTimeMillis();
-                Long time = System.currentTimeMillis() - 10;
+                long now = System.currentTimeMillis();
+                long time = now - 10;
                 recorder.incrProcessTimeTotal(now, time);
                 Assertions.assertNotNull(recorder.getProcessTimeTotal());
                 Assertions.assertEquals(new Long(now - time), recorder.getProcessTimeTotal());
@@ -622,9 +622,9 @@ class CalculateMaxReplicateLagTest {
 
             @Test
             public void testCalculateMaxReplicateLag() {
-                Long time = System.currentTimeMillis();
+                long time = System.currentTimeMillis();
                 List<Long> list = new ArrayList<>();
-                list.add(System.currentTimeMillis() - 10);
+                list.add(time - 10);
                 recorder.calculateMaxReplicateLag(time, list);
                 Assertions.assertNotNull(recorder.getReplicateLagTotal());
                 Assertions.assertEquals(new Long(10), recorder.getReplicateLagTotal());
@@ -633,9 +633,9 @@ public void testCalculateMaxReplicateLag() {
             @Test
             public void testCalculateMaxReplicateLagNotNullOfReplicateLagTotal() {
                 recorder.setReplicateLagTotal(0L);
-                Long time = System.currentTimeMillis();
+                long time = System.currentTimeMillis();
                 List<Long> list = new ArrayList<>();
-                list.add(System.currentTimeMillis() - 10);
+                list.add(time - 10);
                 recorder.calculateMaxReplicateLag(time, list);
                 Assertions.assertNotNull(recorder.getReplicateLagTotal());
                 Assertions.assertEquals(new Long(10), recorder.getReplicateLagTotal());
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/webhook/dto/WebHookAlterInfoDtoTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/dto/WebHookAlterInfoDtoTest.java
new file mode 100644
index 0000000000..8838ab00aa
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/dto/WebHookAlterInfoDtoTest.java
@@ -0,0 +1,22 @@
+package com.tapdata.tm.webhook.dto;
+
+import com.tapdata.tm.alarm.constant.AlarmComponentEnum;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+
+class WebHookAlterInfoDtoTest {
+    @Test
+    void testWithStatusTxt() {
+        WebHookAlterInfoDto dto = new WebHookAlterInfoDto();
+        dto.setComponent(AlarmComponentEnum.FE);
+        dto.withComponentTxt(AlarmComponentEnum.FE);
+        Assertions.assertEquals("引擎", dto.getComponentTxt());
+    }
+    @Test
+    void testWithOtherStatusTxt() {
+        WebHookAlterInfoDto dto = new WebHookAlterInfoDto();
+        dto.withComponentTxt(null);
+        Assertions.assertEquals("-", dto.getComponentTxt());
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/webhook/entity/WebHookEventTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/entity/WebHookEventTest.java
new file mode 100644
index 0000000000..376adb61a9
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/entity/WebHookEventTest.java
@@ -0,0 +1,28 @@
+package com.tapdata.tm.webhook.entity;
+
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class WebHookEventTest {
+    @Test
+    void testAll() {
+        WebHookEvent of = WebHookEvent.of();
+        of.withTitle("title")
+                .withContent("content")
+                .withMetric("")
+                .withUserId(null)
+                .withEvent("")
+                .withType("");
+        Assertions.assertEquals("", of.getType());
+        Assertions.assertEquals("title", of.getTitle());
+        Assertions.assertEquals("content", of.getContent());
+        Assertions.assertNull(of.getUserId());
+        Assertions.assertEquals("", of.getMetric());
+        Assertions.assertEquals("", of.getEvent());
+        of.setContent("c");
+        of.setTitle("t");
+        Assertions.assertEquals("t", of.getTitle());
+        Assertions.assertEquals("c", of.getContent());
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/webhook/enums/WebHookHistoryStatusTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/enums/WebHookHistoryStatusTest.java
new file mode 100644
index 0000000000..201e85c4ef
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/enums/WebHookHistoryStatusTest.java
@@ -0,0 +1,24 @@
+package com.tapdata.tm.webhook.enums;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class WebHookHistoryStatusTest {
+    @Test
+    void testParams() {
+        WebHookHistoryStatus[] values = WebHookHistoryStatus.values();
+        Assertions.assertEquals(3, values.length);
+    }
+    @Test
+    void testFAILED() {
+        Assertions.assertEquals("FAILED", WebHookHistoryStatus.FAILED.name());
+    }
+    @Test
+    void testING() {
+        Assertions.assertEquals("ING", WebHookHistoryStatus.ING.name());
+    }
+    @Test
+    void testSUCCEED() {
+        Assertions.assertEquals("SUCCEED", WebHookHistoryStatus.SUCCEED.name());
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/AdapterServiceTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/AdapterServiceTest.java
new file mode 100644
index 0000000000..c36a74f0db
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/AdapterServiceTest.java
@@ -0,0 +1,69 @@
+package com.tapdata.tm.webhook.server.impl;
+
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+class AdapterServiceTest {
+    AdapterService service;
+    @BeforeEach
+    void init() {
+        service = new AdapterService();
+    }
+
+    @Test
+    void testSend() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.send(null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+
+    @Test
+    void testSendAsync() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.sendAsync(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+
+    @Test
+    void testSendAndSave() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.sendAndSave(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+
+    @Test
+    void testSend2() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.send(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/HistoryServiceTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/HistoryServiceTest.java
new file mode 100644
index 0000000000..4d9187df34
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/HistoryServiceTest.java
@@ -0,0 +1,82 @@
+package com.tapdata.tm.webhook.server.impl;
+
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+class HistoryServiceTest {
+    HistoryService service;
+    @BeforeEach
+    void init() {
+        service = new HistoryService();
+    }
+
+    @Test
+    void testList() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.list(null, null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+
+    @Test
+    void testDeleteHookHistory() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.deleteHookHistory(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+
+    @Test
+    void testReSend() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.reSend(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+
+    @Test
+    void testPushManyHistory() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.pushManyHistory(null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+
+    @Test
+    void testPushHistory() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.pushHistory(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/HttpUtilServiceTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/HttpUtilServiceTest.java
new file mode 100644
index 0000000000..f07c7ef06c
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/HttpUtilServiceTest.java
@@ -0,0 +1,55 @@
+package com.tapdata.tm.webhook.server.impl;
+
+
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+class HttpUtilServiceTest {
+    HttpUtilService service;
+    @BeforeEach
+    void init() {
+        service = new HttpUtilService();
+    }
+
+    @Test
+    void testCheckURL() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.checkURL(null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testPost() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.post(null, null, null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testPost2() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.post(null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/WebHookDefaultServiceTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/WebHookDefaultServiceTest.java
new file mode 100644
index 0000000000..31c378b90a
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/webhook/server/impl/WebHookDefaultServiceTest.java
@@ -0,0 +1,150 @@
+package com.tapdata.tm.webhook.server.impl;
+
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+class WebHookDefaultServiceTest {
+    WebHookDefaultService service;
+    @BeforeEach
+    void init() {
+        service = new WebHookDefaultService();
+    }
+
+    @Test
+    void testList() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.list(null, null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testFindWebHookByHookId() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.findWebHookByHookId(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testCreate() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.create(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testUpdate() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.update(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testUpdatePingResult() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.updatePingResult(null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testClose() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.close(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testDelete() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.delete(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testFindMyOpenHookInfoList() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.findMyOpenHookInfoList(null, null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testCheckUrl() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.checkUrl(null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testPing() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.ping(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+    @Test
+    void testReOpen() {
+        Assertions.assertThrows(BizException.class, () -> {
+            try(MockedStatic<MessageUtil> mu = org.mockito.Mockito.mockStatic(MessageUtil.class)) {
+                mu.when(() -> MessageUtil.getMessage(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION)).thenReturn("message");
+                service.reOpen(null, null);
+            } catch (BizException e) {
+                Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                throw e;
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableNameUtilTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableNameUtilTest.java
new file mode 100644
index 0000000000..f61e78d86c
--- /dev/null
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/dynamic/DynamicTableNameUtilTest.java
@@ -0,0 +1,98 @@
+package com.tapdata.tm.commons.dag.dynamic;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+
+class DynamicTableNameUtilTest {
+
+    @Nested
+    class GetDynamicTableTest {
+        @Test
+        void testDynamicTableNameByDefault() {
+            SimpleDateFormat sdf = new SimpleDateFormat(DynamicTableNameByDate.DATE_FORMAT);
+            String dateFormatStr1 = sdf.format(new Date());
+            DynamicTableResult table = DynamicTableNameUtil.getDynamicTable("table", null);
+            Assertions.assertNotNull(table);
+            String dateFormatStr = sdf.format(new Date());
+            if (dateFormatStr1.equals(dateFormatStr)) {
+                Assertions.assertEquals("table_" + dateFormatStr, table.getDynamicName());
+            }
+            Assertions.assertEquals("table", table.getOldName());
+        }
+        @Test
+        void testDynamicTableNameByPREFIX() {
+            String tableName = "table";
+            DynamicTableConfig rule = DynamicTableConfig.of().withCouplingLocation(CouplingLocation.PREFIX);
+            SimpleDateFormat sdf = new SimpleDateFormat(DynamicTableNameByDate.DATE_FORMAT);
+            String dateFormatStr1 = sdf.format(new Date());
+            DynamicTableResult table = DynamicTableNameUtil.getDynamicTable(tableName, rule);
+            Assertions.assertNotNull(table);
+            String dateFormatStr = sdf.format(new Date());
+            if (dateFormatStr.equals(dateFormatStr1)) {
+                Assertions.assertEquals(dateFormatStr1 + DynamicTableStage.DEFAULT_COUPLING_SYMBOLS + tableName, table.getDynamicName());
+            }
+            Assertions.assertEquals(tableName, table.getOldName());
+        }
+        @Test
+        void testDynamicTableNameByUnKnowRuleType() {
+            String tableName = "table";
+            DynamicTableConfig rule = DynamicTableConfig.of()
+                    .withCouplingLocation(CouplingLocation.SUFFIX)
+                    .withRuleType("xxx");
+            rule.setCouplingLocation(CouplingLocation.SUFFIX);
+            rule.setCouplingSymbols(null);
+            rule.setParams(new HashMap<>());
+            rule.setRuleType("xxx");
+            SimpleDateFormat sdf = new SimpleDateFormat(DynamicTableNameByDate.DATE_FORMAT);
+            String dateFormatStr = sdf.format(new Date());
+            DynamicTableResult table = DynamicTableNameUtil.getDynamicTable(tableName, rule);
+            Assertions.assertNotNull(table);
+            String dateFormatStr1 = sdf.format(new Date());
+            if (dateFormatStr.equals(dateFormatStr1)) {
+                Assertions.assertEquals( tableName + DynamicTableStage.DEFAULT_COUPLING_SYMBOLS + dateFormatStr1, table.getDynamicName());
+            }
+            Assertions.assertEquals(tableName, table.getOldName());
+            Assertions.assertNotNull(rule.getParams());
+        }
+        @Test
+        void testDynamicTableNameByCouplingSymbols0() {
+            String tableName = "table";
+            DynamicTableConfig rule = DynamicTableConfig.of()
+                    .withCouplingLocation(CouplingLocation.SUFFIX)
+                    .withRuleType("default")
+                    .withCouplingSymbols("0");
+            SimpleDateFormat sdf = new SimpleDateFormat(DynamicTableNameByDate.DATE_FORMAT);
+            String dateFormatStr = sdf.format(new Date());
+            DynamicTableResult table = DynamicTableNameUtil.getDynamicTable(tableName, rule);
+            Assertions.assertNotNull(table);
+            String dateFormatStr1 = sdf.format(new Date());
+            if (dateFormatStr.equals(dateFormatStr1)) {
+                Assertions.assertEquals( tableName + "0" + dateFormatStr1, table.getDynamicName());
+            }
+            Assertions.assertEquals(tableName, table.getOldName());
+        }
+        @Test
+        void testDynamicTableNameByCustomParams() {
+            String tableName = "table";
+            DynamicTableConfig rule = DynamicTableConfig.of()
+                    .withCouplingLocation(CouplingLocation.SUFFIX)
+                    .withRuleType("default")
+                    .withCouplingSymbols("0")
+                    .withParams(new HashMap<>());
+            SimpleDateFormat sdf = new SimpleDateFormat(DynamicTableNameByDate.DATE_FORMAT);
+            String dateFormatStr = sdf.format(new Date());
+            DynamicTableResult table = DynamicTableNameUtil.getDynamicTable(tableName, rule);
+            Assertions.assertNotNull(table);
+            String dateFormatStr1 = sdf.format(new Date());
+            if (dateFormatStr.equals(dateFormatStr1)) {
+                Assertions.assertEquals( tableName + "0" + dateFormatStr1, table.getDynamicName());
+            }
+            Assertions.assertEquals(tableName, table.getOldName());
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/nodes/TableNodeTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/nodes/TableNodeTest.java
new file mode 100644
index 0000000000..3a5e0891a8
--- /dev/null
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/nodes/TableNodeTest.java
@@ -0,0 +1,237 @@
+package com.tapdata.tm.commons.dag.nodes;
+
+import com.tapdata.tm.commons.dag.dynamic.DynamicTableConfig;
+import com.tapdata.tm.commons.dag.dynamic.DynamicTableNameUtil;
+import com.tapdata.tm.commons.dag.dynamic.DynamicTableResult;
+import com.tapdata.tm.commons.schema.Schema;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+class TableNodeTest {
+    TableNode node;
+    DynamicTableConfig dynamicTableRule;
+    Boolean needDynamicTableName;
+    Schema schema;
+
+    @BeforeEach
+    void init() {
+        schema = new Schema();
+        node = mock(TableNode.class);
+        dynamicTableRule = mock(DynamicTableConfig.class);
+        ReflectionTestUtils.setField(node, "dynamicTableRule", dynamicTableRule);
+        ReflectionTestUtils.setField(node, "tableName", "table");
+        ReflectionTestUtils.setField(node, "needDynamicTableName", needDynamicTableName);
+    }
+    @Nested
+    class DynamicTableNameTest {
+        DynamicTableResult dynamicTable;
+        @BeforeEach
+        void init() {
+            dynamicTable = DynamicTableResult.of().withDynamicName("newName").withOldTableName("table");
+            when(node.getSchemaName(schema)).thenReturn("table");
+            doCallRealMethod().when(node).dynamicTableName(schema);
+            when(node.getTableName()).thenCallRealMethod();
+            when(node.getDynamicTableRule()).thenCallRealMethod();
+            when(node.getNeedDynamicTableName()).thenCallRealMethod();
+        }
+
+        @Test
+        void testGetNeedDynamicTableName() {
+            Boolean needDynamicTableName = node.getNeedDynamicTableName();
+            Assertions.assertNull(needDynamicTableName);
+        }
+
+        @Test
+        void testGetRule() {
+            DynamicTableConfig dynamicTableRule = node.getDynamicTableRule();
+            Assertions.assertEquals(dynamicTableRule, dynamicTableRule);
+        }
+
+        @Test
+        void testNotNeedDynamicTableName() {
+            try(MockedStatic<DynamicTableNameUtil> dtnu = mockStatic(DynamicTableNameUtil.class)) {
+                dtnu.when(() -> DynamicTableNameUtil.getDynamicTable(anyString(), any(DynamicTableConfig.class))).thenReturn(dynamicTable);
+                Assertions.assertDoesNotThrow(() -> node.dynamicTableName(schema));
+                Assertions.assertEquals("table", node.getTableName());
+            }
+        }
+        @Test
+        void testNotNeedDynamicTableNameSchemaIsNull() {
+            doCallRealMethod().when(node).dynamicTableName(null);
+            try(MockedStatic<DynamicTableNameUtil> dtnu = mockStatic(DynamicTableNameUtil.class)) {
+                dtnu.when(() -> DynamicTableNameUtil.getDynamicTable(anyString(), any(DynamicTableConfig.class))).thenReturn(dynamicTable);
+                Assertions.assertDoesNotThrow(() -> node.dynamicTableName(null));
+                Assertions.assertNull(node.getTableName());
+            }
+        }
+        @Test
+        void testNeedDynamicTableNameButResultIsEmpty() {
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            try(MockedStatic<DynamicTableNameUtil> dtnu = mockStatic(DynamicTableNameUtil.class)) {
+                dtnu.when(() -> DynamicTableNameUtil.getDynamicTable(anyString(), any(DynamicTableConfig.class))).thenReturn(null);
+                Assertions.assertDoesNotThrow(() -> node.dynamicTableName(schema));
+                Assertions.assertEquals("table", node.getTableName());
+            }
+        }
+
+        @Test
+        void testNeedDynamicTableName() {
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            try(MockedStatic<DynamicTableNameUtil> dtnu = mockStatic(DynamicTableNameUtil.class)) {
+                dtnu.when(() -> DynamicTableNameUtil.getDynamicTable(anyString(), any(DynamicTableConfig.class))).thenReturn(dynamicTable);
+                Assertions.assertDoesNotThrow(() -> node.dynamicTableName(schema));
+                Assertions.assertEquals("newName", node.getTableName());
+            }
+        }
+
+        @Test
+        void testNeedDynamicTableNameSchemaIsNull() {
+            doCallRealMethod().when(node).dynamicTableName(null);
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            try(MockedStatic<DynamicTableNameUtil> dtnu = mockStatic(DynamicTableNameUtil.class)) {
+                dtnu.when(() -> DynamicTableNameUtil.getDynamicTable(anyString(), any(DynamicTableConfig.class))).thenReturn(dynamicTable);
+                Assertions.assertDoesNotThrow(() -> node.dynamicTableName(null));
+                Assertions.assertEquals("newName", node.getTableName());
+            }
+        }
+        @Test
+        void testDynamicTableConfigIsNull() {
+            ReflectionTestUtils.setField(node, "dynamicTableRule", null);
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            try(MockedStatic<DynamicTableNameUtil> dtnu = mockStatic(DynamicTableNameUtil.class)) {
+                dtnu.when(() -> DynamicTableNameUtil.getDynamicTable(anyString(), any(DynamicTableConfig.class))).thenReturn(dynamicTable);
+                Assertions.assertDoesNotThrow(() -> node.dynamicTableName(schema));
+                Assertions.assertEquals("newName", node.getTableName());
+            }
+        }
+        @Test
+        void testTableNameEqualsBeforeNameInSchema() {
+            ReflectionTestUtils.setField(node, "dynamicTableRule", null);
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            schema.setAfterDynamicTableName("table");
+            try(MockedStatic<DynamicTableNameUtil> dtnu = mockStatic(DynamicTableNameUtil.class)) {
+                dtnu.when(() -> DynamicTableNameUtil.getDynamicTable(anyString(), any(DynamicTableConfig.class))).thenReturn(dynamicTable);
+                Assertions.assertDoesNotThrow(() -> node.dynamicTableName(schema));
+                Assertions.assertEquals("newName", node.getTableName());
+            }
+        }
+        @Test
+        void testTableNameIsNull() {
+            ReflectionTestUtils.setField(node, "dynamicTableRule", null);
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            ReflectionTestUtils.setField(node, "tableName", null);
+            schema.setAfterDynamicTableName("table");
+            try(MockedStatic<DynamicTableNameUtil> dtnu = mockStatic(DynamicTableNameUtil.class)) {
+                dtnu.when(() -> DynamicTableNameUtil.getDynamicTable(anyString(), any(DynamicTableConfig.class))).thenReturn(dynamicTable);
+                Assertions.assertDoesNotThrow(() -> node.dynamicTableName(schema));
+                Assertions.assertEquals("newName", node.getTableName());
+            }
+        }
+    }
+
+    @Nested
+    class GetSchemaNameTest {
+        @BeforeEach
+        void init() {
+            when(node.getSchemaName(schema)).thenCallRealMethod();
+            when(node.getSchemaName(null)).thenCallRealMethod();
+        }
+
+        @Test
+        void testOldTableNameIsNull() {
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testOldTableNameNotNull() {
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testDynamicTableConfigIsNull() {
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            ReflectionTestUtils.setField(node, "dynamicTableRule", null);
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testDynamicTableConfigNotNullButAfterDynamicTableNameNotNull() {
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            DynamicTableConfig of = DynamicTableConfig.of();
+            ReflectionTestUtils.setField(node, "dynamicTableRule", of);
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testDynamicTableConfigNotNullButAfterDynamicTableNameIsNull() {
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            DynamicTableConfig of = DynamicTableConfig.of();
+            ReflectionTestUtils.setField(node, "dynamicTableRule", of);
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testDynamicTableConfigNotNullButAfterDynamicTableNameEqualsTableName() {
+            ReflectionTestUtils.setField(node, "needDynamicTableName", true);
+            DynamicTableConfig of = DynamicTableConfig.of();
+            ReflectionTestUtils.setField(node, "dynamicTableRule", of);
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testDynamicTableNameIsNull() {
+            ReflectionTestUtils.setField(node, "dynamicTableRule", null);
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testBeforeDynamicTableNameNotNullInSchema() {
+            schema.setBeforeDynamicTableName("table");
+            ReflectionTestUtils.setField(node, "dynamicTableRule", null);
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testAfterDynamicTableNameNotNullInSchema() {
+            schema.setAfterDynamicTableName("table");
+            DynamicTableConfig of = DynamicTableConfig.of();
+            ReflectionTestUtils.setField(node, "dynamicTableRule", of);
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testAfterDynamicTableNameNotEqualsTableName() {
+            schema.setAfterDynamicTableName("newTable");
+            DynamicTableConfig of = DynamicTableConfig.of();
+            ReflectionTestUtils.setField(node, "dynamicTableRule", of);
+            Assertions.assertEquals("table", node.getSchemaName(schema));
+        }
+        @Test
+        void testSchemaIsNull() {
+            ReflectionTestUtils.setField(node, "dynamicTableRule", null);
+            Assertions.assertEquals("table", node.getSchemaName(null));
+        }
+    }
+
+    @Nested
+    class UpdateSchemaAfterDynamicTableNameTest {
+        @Test
+        void testNormal() {
+            doCallRealMethod().when(node).updateSchemaAfterDynamicTableName(schema, "table", "newTable");
+            node.updateSchemaAfterDynamicTableName(schema, "table", "newTable");
+            Assertions.assertEquals("table", schema.getBeforeDynamicTableName());
+            Assertions.assertEquals("newTable", schema.getAfterDynamicTableName());
+        }
+        @Test
+        void testNull() {
+            String before = schema.getBeforeDynamicTableName();
+            String after = schema.getAfterDynamicTableName();
+            doCallRealMethod().when(node).updateSchemaAfterDynamicTableName(null, "table", "newTable");
+            node.updateSchemaAfterDynamicTableName(null, "table", "newTable");
+            Assertions.assertEquals(before, schema.getBeforeDynamicTableName());
+            Assertions.assertEquals(after, schema.getAfterDynamicTableName());
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/schema/SchemaUtilsTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/schema/SchemaUtilsTest.java
new file mode 100644
index 0000000000..a5e34478e5
--- /dev/null
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/schema/SchemaUtilsTest.java
@@ -0,0 +1,158 @@
+package com.tapdata.tm.commons.schema;
+
+
+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 java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class SchemaUtilsTest {
+    @Nested
+    class RemoveSubFieldsWhichFromFreeSchemaTest {
+        DataSourceConnectionDto dataSource;
+        List<Schema> schemas;
+        Schema schema;
+        List<String> definitionTags;
+        List<Field> fields;
+        Field field;
+        @BeforeEach
+        void init() {
+            schema = mock(Schema.class);
+            field = mock(Field.class);
+            dataSource = new DataSourceConnectionDto();
+            definitionTags = new ArrayList<>();
+            schemas = new ArrayList<>();
+            fields = new ArrayList<>();
+            schemas.add(schema);
+            fields.add(field);
+            when(schema.getFields()).thenReturn(fields);
+            when(field.getFieldName()).thenReturn("name");
+            dataSource.setDefinitionTags(definitionTags);
+        }
+
+        @DisplayName("The test target is not a schema free type database")
+        @Test
+        void testOneSchema() {
+            Schema s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schema);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(schema, s);
+        }
+
+        @DisplayName("The test target is not a schema free type database")
+        @Test
+        void testAllSchema() {
+            List<Schema> s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schemas);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(1, s.size());
+            Assertions.assertEquals(schema, s.get(0));
+        }
+
+        @DisplayName("The test target is a schema free type database")
+        @Test
+        void testDataBaseIsFreeSchemaOne() {
+            definitionTags.add(SchemaUtils.SCHEMA_FREE);
+            Schema s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schema);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(schema, s);
+        }
+
+        @DisplayName("The test target is a schema free type database")
+        @Test
+        void testDataBaseIsFreeSchemaALl() {
+            definitionTags.add(SchemaUtils.SCHEMA_FREE);
+            List<Schema> s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schemas);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(1, s.size());
+            Assertions.assertEquals(schema, s.get(0));
+        }
+
+        @DisplayName("The test target is not a schema-free type database")
+        @Test
+        void testDataBaseNotFreeSchemaOne() {
+            definitionTags.add("database");
+            Schema s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schema);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(schema, s);
+        }
+
+        @DisplayName("The test target is not a schema free type database")
+        @Test
+        void testDataBaseNotFreeSchemaALl() {
+            definitionTags.add("database");
+            List<Schema> s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schemas);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(1, s.size());
+            Assertions.assertEquals(schema, s.get(0));
+        }
+
+        @DisplayName("The test schema does not contain any fields")
+        @Test
+        void testSchemaNotAnyFields() {
+            fields.clear();
+            definitionTags.add(SchemaUtils.SCHEMA_FREE);
+            List<Schema> s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schemas);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(1, s.size());
+            Assertions.assertEquals(schema, s.get(0));
+        }
+
+        @DisplayName("The test schema does not contain any sub attribute fields")
+        @Test
+        void testContainsSubFields() {
+            Field fieldSub = mock(Field.class);
+            fields.add(fieldSub);
+            definitionTags.add(SchemaUtils.SCHEMA_FREE);
+            when(fieldSub.getFieldName()).thenReturn("name.id");
+            List<Schema> s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schemas);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(1, s.size());
+            Assertions.assertEquals(schema, s.get(0));
+        }
+
+        @DisplayName("Test attribute names with dots that do not contain parent attributes")
+        @Test
+        void testContainsSubFieldsButNotFatherField() {
+            definitionTags.add(SchemaUtils.SCHEMA_FREE);
+            when(field.getFieldName()).thenReturn("name.id");
+            List<Schema> s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schemas);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(1, s.size());
+            Assertions.assertEquals(schema, s.get(0));
+        }
+
+        @DisplayName("The test parent property is TapMap")
+        @Test
+        void testContainsSubFieldsButNotFatherFieldAndFieldIsTAP_MAP() {
+            Field fieldSub = mock(Field.class);
+            when(field.getTapType()).thenReturn(SchemaUtils.TAP_MAP);
+            fields.add(fieldSub);
+            definitionTags.add(SchemaUtils.SCHEMA_FREE);
+            when(fieldSub.getFieldName()).thenReturn("name.id");
+            List<Schema> s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schemas);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(1, s.size());
+            Assertions.assertEquals(schema, s.get(0));
+        }
+
+        @DisplayName("The test parent property is TapArray")
+        @Test
+        void testContainsSubFieldsButNotFatherFieldAndFieldIsTAP_ARRAY() {
+            Field fieldSub = mock(Field.class);
+            when(field.getTapType()).thenReturn(SchemaUtils.TAP_ARRAY);
+            fields.add(fieldSub);
+            definitionTags.add(SchemaUtils.SCHEMA_FREE);
+            when(fieldSub.getFieldName()).thenReturn("name.id");
+            List<Schema> s = SchemaUtils.removeSubFieldsWhichFromFreeSchema(dataSource, schemas);
+            Assertions.assertNotNull(s);
+            Assertions.assertEquals(1, s.size());
+            Assertions.assertEquals(schema, s.get(0));
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/util/ConnHeartbeatUtilsTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/util/ConnHeartbeatUtilsTest.java
new file mode 100644
index 0000000000..490501a3e5
--- /dev/null
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/util/ConnHeartbeatUtilsTest.java
@@ -0,0 +1,74 @@
+package com.tapdata.tm.commons.util;
+
+import com.tapdata.tm.commons.dag.AccessNodeTypeEnum;
+import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
+import com.tapdata.tm.commons.schema.DataSourceDefinitionDto;
+import org.junit.jupiter.api.DisplayName;
+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.*;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-05-10 19:21
+ **/
+@DisplayName("Class ConnHeartbeatUtils Test")
+class ConnHeartbeatUtilsTest {
+
+	@Test
+	@DisplayName("Method generateConnections test")
+	void testGenerateConnections() {
+		DataSourceDefinitionDto dataSourceDefinitionDto = new DataSourceDefinitionDto();
+		String testId = "test_id";
+		String type = "test_type";
+		String pdkHash = "test_pdk_hash";
+		dataSourceDefinitionDto.setType(type);
+		dataSourceDefinitionDto.setPdkHash(pdkHash);
+
+		DataSourceConnectionDto dataSourceConnectionDto = ConnHeartbeatUtils.generateConnections(testId, dataSourceDefinitionDto);
+
+		assertNotNull(dataSourceConnectionDto);
+		assertEquals(ConnHeartbeatUtils.CONNECTION_NAME, dataSourceConnectionDto.getName());
+		assertEquals(DataSourceConnectionDto.STATUS_READY, dataSourceConnectionDto.getStatus());
+		assertEquals("source", dataSourceConnectionDto.getConnection_type());
+		assertEquals(CreateTypeEnum.System, dataSourceConnectionDto.getCreateType());
+		assertEquals(dataSourceDefinitionDto.getType(), dataSourceConnectionDto.getDatabase_type());
+		assertEquals(DataSourceDefinitionDto.PDK_TYPE, dataSourceConnectionDto.getPdkType());
+		assertEquals(dataSourceDefinitionDto.getPdkHash(), dataSourceConnectionDto.getPdkHash());
+		assertEquals(0, dataSourceConnectionDto.getRetry());
+		assertEquals(AccessNodeTypeEnum.AUTOMATIC_PLATFORM_ALLOCATION.name(), dataSourceConnectionDto.getAccessNodeType());
+		Map<String, Object> config = dataSourceConnectionDto.getConfig();
+		assertNotNull(config);
+		assertEquals(ConnHeartbeatUtils.MODE, config.get("mode"));
+		assertEquals(1, config.get("initial_totals"));
+		assertEquals(1000, config.get("incremental_interval"));
+		assertEquals(1, config.get("incremental_interval_totals"));
+		Object incrementalTypes = config.get("incremental_types");
+		assertInstanceOf(int[].class, incrementalTypes);
+		assertEquals(1, ((int[]) incrementalTypes).length);
+		assertEquals(1, ((int[]) incrementalTypes)[0]);
+		assertEquals(ConnHeartbeatUtils.TABLE_NAME, config.get("table_name"));
+		Object tableFieldsObj = config.get("table_fields");
+		assertInstanceOf(ArrayList.class, tableFieldsObj);
+		List<?> tableFields = (ArrayList<?>) tableFieldsObj;
+		assertEquals(2, tableFields.size());
+		Object obj1 = tableFields.get(0);
+		assertInstanceOf(HashMap.class, obj1);
+		Map<?, ?> map1 = (HashMap<?, ?>) obj1;
+		assertTrue((Boolean) map1.get("pri"));
+		assertEquals("id", map1.get("name"));
+		assertEquals("string(64)", map1.get("type"));
+		Object obj2 = tableFields.get(1);
+		assertInstanceOf(HashMap.class, obj2);
+		Map<?, ?> map2 = (HashMap<?, ?>) obj2;
+		assertFalse((Boolean) map2.get("pri"));
+		assertEquals("ts", map2.get("name"));
+		assertEquals("now", map2.get("type"));
+	}
+}
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java
index 645807f293..e7e1311f3d 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java
@@ -1,15 +1,69 @@
 package com.tapdata.tm.metadatainstance.service;
 
+import cn.hutool.core.bean.BeanUtil;
+import com.mongodb.bulk.BulkWriteResult;
+import com.mongodb.client.result.UpdateResult;
+import com.tapdata.tm.base.dto.Filter;
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.dto.Where;
 import com.tapdata.tm.base.exception.BizException;
-import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
-import com.tapdata.tm.commons.schema.MetadataInstancesDto;
+import com.tapdata.tm.commons.dag.DAG;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.logCollector.LogCollecotrConnConfig;
+import com.tapdata.tm.commons.dag.logCollector.LogCollectorNode;
+import com.tapdata.tm.commons.dag.logCollector.VirtualTargetNode;
+import com.tapdata.tm.commons.dag.nodes.DataNode;
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
+import com.tapdata.tm.commons.dag.nodes.TableNode;
+import com.tapdata.tm.commons.dag.process.MergeTableNode;
+import com.tapdata.tm.commons.dag.process.MigrateFieldRenameProcessorNode;
+import com.tapdata.tm.commons.dag.vo.FieldChangeRule;
+import com.tapdata.tm.commons.dag.vo.SyncObjects;
+import com.tapdata.tm.commons.schema.*;
+import com.tapdata.tm.commons.schema.bean.Schema;
 import com.tapdata.tm.commons.schema.bean.SourceDto;
+import com.tapdata.tm.commons.schema.bean.Table;
+import com.tapdata.tm.commons.task.dto.TaskDto;
 import com.tapdata.tm.commons.util.MetaDataBuilderUtils;
 import com.tapdata.tm.commons.util.MetaType;
+import com.tapdata.tm.commons.util.PdkSchemaConvert;
 import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.discovery.bean.DiscoveryFieldDto;
+import com.tapdata.tm.ds.service.impl.DataSourceDefinitionServiceImpl;
 import com.tapdata.tm.ds.service.impl.DataSourceService;
 import com.tapdata.tm.ds.service.impl.DataSourceServiceImpl;
+import com.tapdata.tm.metadatainstance.bean.MultiPleTransformReq;
+import com.tapdata.tm.metadatainstance.dto.DataType2TapTypeDto;
+import com.tapdata.tm.metadatainstance.dto.DataTypeCheckMultipleVo;
+import com.tapdata.tm.metadatainstance.entity.MetadataInstancesEntity;
+import com.tapdata.tm.metadatainstance.param.ClassificationParam;
+import com.tapdata.tm.metadatainstance.param.TablesSupportInspectParam;
 import com.tapdata.tm.metadatainstance.repository.MetadataInstancesRepository;
+import com.tapdata.tm.metadatainstance.vo.MetaTableCheckVo;
+import com.tapdata.tm.metadatainstance.vo.MetaTableVo;
+import com.tapdata.tm.metadatainstance.vo.MetadataInstancesVo;
+import com.tapdata.tm.metadatainstance.vo.TableSupportInspectVo;
+import com.tapdata.tm.permissions.DataPermissionHelper;
+import com.tapdata.tm.permissions.IDataPermissionHelper;
+import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.user.dto.UserDto;
+import com.tapdata.tm.user.service.UserService;
+import com.tapdata.tm.utils.MetadataUtil;
+import com.tapdata.tm.utils.MongoUtils;
+import com.tapdata.tm.utils.SchemaTransformUtils;
+import io.tapdata.entity.conversion.TableFieldTypesGenerator;
+import io.tapdata.entity.mapping.DefaultExpressionMatchingMap;
+import io.tapdata.entity.mapping.TypeExprResult;
+import io.tapdata.entity.mapping.type.TapStringMapping;
+import io.tapdata.entity.schema.TapField;
+import io.tapdata.entity.schema.TapTable;
+import io.tapdata.entity.schema.type.TapType;
+import io.tapdata.entity.utils.DataMap;
+import io.tapdata.entity.utils.InstanceFactory;
+import io.tapdata.pdk.core.constants.SystemConstants;
+import io.tapdata.pdk.core.utils.CommonUtils;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.tuple.Pair;
 import org.bson.Document;
 import org.bson.types.ObjectId;
 import org.junit.jupiter.api.BeforeEach;
@@ -17,13 +71,20 @@
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.internal.verification.Times;
+import org.springframework.data.mongodb.core.BulkOperations;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.aggregation.Aggregation;
+import org.springframework.data.mongodb.core.aggregation.AggregationResults;
+import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
+import static com.tapdata.tm.utils.MongoUtils.toObjectId;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
@@ -43,15 +104,13 @@ public class MetadataInstancesServiceImplTest {
 	void setUp() {
 		metadataInstancesRepository = mock(MetadataInstancesRepository.class);
 		metadataInstancesService = new MetadataInstancesServiceImpl(metadataInstancesRepository);
+		metadataInstancesService = spy(metadataInstancesService);
 		userDetail = mock(UserDetail.class);
+		new DataPermissionHelper(mock(IDataPermissionHelper.class));
 	}
 
 	@Nested
 	class checkSetLastUpdateTest {
-		@BeforeEach
-		void setUp() {
-			metadataInstancesService = spy(metadataInstancesService);
-		}
 
 		@Test
 		@DisplayName("Test main process")
@@ -171,7 +230,6 @@ class findDatabaseMetadataInstanceLastUpdateTest {
 		void setUp() {
 			dataSourceService = mock(DataSourceServiceImpl.class);
 			ReflectionTestUtils.setField(metadataInstancesService, "dataSourceService", dataSourceService);
-			metadataInstancesService = spy(metadataInstancesService);
 		}
 
 		@Test
@@ -236,4 +294,2436 @@ void testFindDatabaseMetadataReturnNull() {
 			}
 		}
 	}
+	@Nested
+	class ModifyByIdTest{
+		@Test
+		void testModifyByIdNormal(){
+			ObjectId id = mock(ObjectId.class);
+			MetadataInstancesDto record = new MetadataInstancesDto();
+			MetadataInstancesEntity entity = new MetadataInstancesEntity();
+			doReturn(entity).when(metadataInstancesRepository).save(any(MetadataInstancesEntity.class),any(UserDetail.class));
+			MetadataInstancesDto actual = metadataInstancesService.modifyById(id, record, userDetail);
+			verify(metadataInstancesService,new Times(2)).beforeCreateOrUpdate(record,userDetail);
+			verify(metadataInstancesService,new Times(1)).beforeUpdateById(id,record);
+			verify(metadataInstancesService,new Times(1)).save(record,userDetail);
+			verify(metadataInstancesService,new Times(1)).afterUpdateById(id,record);
+			assertEquals(null,actual);
+		}
+	}
+	@Nested
+	class beforeSaveTest{
+		@Test
+		@DisplayName("test beforeSave method when record is null")
+		void test1(){
+			MetadataInstancesDto record = null;
+			try (MockedStatic<ObjectId> mb = Mockito
+					.mockStatic(ObjectId.class)) {
+				mb.when(ObjectId::get).thenReturn(mock(ObjectId.class));
+				metadataInstancesService.beforeSave(record,userDetail);
+				mb.verify(() -> ObjectId.get(),new Times(0));
+				verify(metadataInstancesService,new Times(0)).beforeCreateOrUpdate(record,userDetail);
+			}
+		}
+		@Test
+		@DisplayName("test beforeSave method when record fields is empty")
+		void test2(){
+			MetadataInstancesDto record = new MetadataInstancesDto();
+			ArrayList<Field> fields = new ArrayList<>();
+			record.setFields(fields);
+			try (MockedStatic<ObjectId> mb = Mockito
+					.mockStatic(ObjectId.class)) {
+				mb.when(ObjectId::get).thenReturn(mock(ObjectId.class));
+				metadataInstancesService.beforeSave(record,userDetail);
+				mb.verify(() -> ObjectId.get(),new Times(0));
+				verify(metadataInstancesService,new Times(1)).beforeCreateOrUpdate(record,userDetail);
+			}
+		}
+		@Test
+		@DisplayName("test beforeSave method when record fields is not empty")
+		void test3(){
+			MetadataInstancesDto record = new MetadataInstancesDto();
+			ArrayList<Field> fields = new ArrayList<>();
+			fields.add(new Field());
+			Field field = new Field();
+			field.setId("111");
+			fields.add(field);
+			record.setFields(fields);
+			try (MockedStatic<ObjectId> mb = Mockito
+					.mockStatic(ObjectId.class)) {
+				mb.when(ObjectId::get).thenReturn(mock(ObjectId.class));
+				metadataInstancesService.beforeSave(record,userDetail);
+				mb.verify(() -> ObjectId.get(),new Times(1));
+				verify(metadataInstancesService,new Times(1)).beforeCreateOrUpdate(record,userDetail);
+			}
+		}
+	}
+	@Nested
+	class ListTest{
+		private Filter filter;
+		@BeforeEach
+		void beforeEach(){
+			filter = new Filter();
+		}
+		@Test
+		@DisplayName("test list method normal")
+		void test1(){
+			Where where = new Where();
+			where.put("source.id","111");
+			Map<String, List> classficitionIn = new HashMap<>();
+			List<String> classficitionIds = new ArrayList<>();
+			classficitionIds.add("65bc933c6129fe73d7858b40");
+			classficitionIn.put("$in",classficitionIds);
+			where.put("classifications.id",classficitionIn);
+			where.put("taskId","333");
+			filter.setWhere(where);
+			when(metadataInstancesRepository.whereToCriteria(where)).thenReturn(mock(Criteria.class));
+			doNothing().when(metadataInstancesService).afterFindAll(anyList());
+			metadataInstancesService.list(filter,userDetail);
+			verify(metadataInstancesService,new Times(1)).findAll(any(Query.class));
+			verify(metadataInstancesService,new Times(0)).find(filter);
+		}
+		@Test
+		@DisplayName("test list method simple")
+		void test2(){
+			Where where = new Where();
+			filter.setWhere(where);
+			when(metadataInstancesRepository.whereToCriteria(where)).thenReturn(mock(Criteria.class));
+			doNothing().when(metadataInstancesService).afterFindAll(anyList());
+			metadataInstancesService.list(filter,userDetail);
+			verify(metadataInstancesService,new Times(0)).findAll(any(Query.class));
+			verify(metadataInstancesService,new Times(1)).find(filter);
+		}
+		@Test
+		@DisplayName("test list method when filter getLimit equals 0")
+		void test3(){
+			Where where = new Where();
+			where.put("taskId","333");
+			filter.setWhere(where);
+			filter.setSize(0);
+			when(metadataInstancesRepository.whereToCriteria(where)).thenReturn(mock(Criteria.class));
+			doNothing().when(metadataInstancesService).afterFindAll(anyList());
+			metadataInstancesService.list(filter,userDetail);
+			verify(metadataInstancesService,new Times(1)).findAll(any(Query.class));
+			verify(metadataInstancesService,new Times(0)).find(filter);
+		}
+	}
+	@Nested
+	class FindInspectTest{
+		private Filter filter;
+		@BeforeEach
+		void beforeEach(){
+			filter = new Filter();
+		}
+		@Test
+		@DisplayName("test findInspect method when metadataInstancesDtoList is not empty")
+		void test1(){
+			Where where = new Where();
+			where.put("source.id","111");
+			filter.setWhere(where);
+			List metadataInstancesDtoList = new ArrayList<>();
+			MetadataInstancesDto metadataInstancesDto = mock(MetadataInstancesDto.class);
+			metadataInstancesDtoList.add(metadataInstancesDto);
+			when(metadataInstancesService.findAll(filter)).thenReturn(metadataInstancesDtoList);
+			List<MetadataInstancesVo> actual = metadataInstancesService.findInspect(filter, userDetail);
+			assertEquals(BeanUtil.copyProperties(metadataInstancesDto, MetadataInstancesVo.class),actual.get(0));
+		}
+		@Test
+		@DisplayName("test findInspect method when metadataInstancesDtoList is empty")
+		void test2(){
+			Where where = new Where();
+			filter.setWhere(where);
+			List<MetadataInstancesVo> actual = metadataInstancesService.findInspect(filter, userDetail);
+			assertEquals(0,actual.size());
+		}
+	}
+	@Nested
+	class QueryByIdTest{
+		@Test
+		void testQueryByIdNormal(){
+			ObjectId id = mock(ObjectId.class);
+			com.tapdata.tm.base.dto.Field fields = mock(com.tapdata.tm.base.dto.Field.class);
+			MetadataInstancesDto metadataInstancesDto = mock(MetadataInstancesDto.class);
+			doReturn(metadataInstancesDto).when(metadataInstancesService).findById(id,fields,userDetail);
+			metadataInstancesService.queryById(id,fields,userDetail);
+			verify(metadataInstancesService).afterFindOne(metadataInstancesDto,userDetail);
+			verify(metadataInstancesService).afterFindById(metadataInstancesDto);
+			verify(metadataInstancesService).afterFind(metadataInstancesDto);
+		}
+	}
+	@Nested
+	class QueryByOneTest{
+		private Filter filter;
+		@BeforeEach
+		void beforeEach(){
+			filter = new Filter();
+		}
+		@Test
+		@DisplayName("test queryByOne method normal")
+		void test1(){
+			Where where = new Where();
+			where.put("source.id","111");
+			filter.setWhere(where);
+			MetadataInstancesDto metadataInstancesDto = mock(MetadataInstancesDto.class);
+			doReturn(metadataInstancesDto).when(metadataInstancesService).findOne(filter,userDetail);
+			metadataInstancesService.queryByOne(filter,userDetail);
+			verify(metadataInstancesService).afterFindOne(metadataInstancesDto,userDetail);
+			verify(metadataInstancesService).afterFind(metadataInstancesDto);
+		}
+		@Test
+		@DisplayName("test queryByOne method when sourceId is null")
+		void test2(){
+			Where where = new Where();
+			filter.setWhere(where);
+			MetadataInstancesDto metadataInstancesDto = mock(MetadataInstancesDto.class);
+			doReturn(metadataInstancesDto).when(metadataInstancesService).findOne(filter,userDetail);
+			metadataInstancesService.queryByOne(filter,userDetail);
+			verify(metadataInstancesService).afterFindOne(metadataInstancesDto,userDetail);
+			verify(metadataInstancesService).afterFind(metadataInstancesDto);
+		}
+	}
+	@Nested
+	class JobStatsTest{
+		@Test
+		void testJobStatsNormal(){
+			long skip = 3L;
+			int limit = 1;
+			MongoTemplate template = mock(MongoTemplate.class);
+			when(metadataInstancesRepository.getMongoOperations()).thenReturn(template);
+			AggregationResults aggregationResults = mock(AggregationResults.class);
+			when(template.aggregate(any(Aggregation.class),anyString(),any(Class.class))).thenReturn(aggregationResults);
+			List result = new ArrayList();
+			MetadataInstancesEntity entity = new MetadataInstancesEntity();
+			ObjectId id = mock(ObjectId.class);
+			entity.setId(id);
+			result.add(entity);
+			when(aggregationResults.getMappedResults()).thenReturn(result);
+			List<MetadataInstancesDto> actual = metadataInstancesService.jobStats(skip, limit);
+			assertEquals(id,actual.get(0).getId());
+		}
+	}
+	@Nested
+	class SchemaTest{
+		private Filter filter = mock(Filter.class);
+		@Test
+		@DisplayName("test schema method when page getTotal equals 0")
+		void test1(){
+			Page<MetadataInstancesDto> page = new Page<>();
+			page.setTotal(0);
+			doReturn(page).when(metadataInstancesService).find(filter,userDetail);
+			List<MetadataInstancesDto> actual = metadataInstancesService.schema(filter, userDetail);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test schema method normal")
+		void test2(){
+			try (MockedStatic<SchemaTransformUtils> mb = Mockito
+					.mockStatic(SchemaTransformUtils.class)) {
+				Schema schema = mock(Schema.class);
+				mb.when(()->SchemaTransformUtils.newSchema2oldSchema(anyList())).thenReturn(schema);
+				Page<MetadataInstancesDto> page = new Page<>();
+				page.setTotal(1);
+				List<MetadataInstancesDto> metadataInstancesDtoList = new ArrayList<>();
+				MetadataInstancesDto meta1 = new MetadataInstancesDto();
+				MetadataInstancesDto meta2 = new MetadataInstancesDto();
+				meta1.setMetaType("mongo_view");
+				meta1.setPipline(mock(Object.class));
+				metadataInstancesDtoList.add(meta1);
+				meta2.setMetaType("job");
+				metadataInstancesDtoList.add(meta2);
+				page.setItems(metadataInstancesDtoList);
+				doReturn(page).when(metadataInstancesService).find(filter,userDetail);
+				List<MetadataInstancesDto> actual = metadataInstancesService.schema(filter, userDetail);
+				assertEquals(2,actual.size());
+				assertEquals(schema,actual.get(0).getSchema());
+				mb.verify(() -> SchemaTransformUtils.newSchema2oldSchema(anyList()),new Times(1));
+			}
+		}
+	}
+	@Nested
+	class LienageTest{
+		@Test
+		void testLienageNormal(){
+			MongoTemplate template = mock(MongoTemplate.class);
+			when(metadataInstancesRepository.getMongoOperations()).thenReturn(template);
+			AggregationResults aggregationResults = mock(AggregationResults.class);
+			when(template.aggregate(any(Aggregation.class),anyString(),any(Class.class))).thenReturn(aggregationResults);
+			List result = new ArrayList();
+			MetadataInstancesEntity entity = new MetadataInstancesEntity();
+			ObjectId id = mock(ObjectId.class);
+			entity.setId(id);
+			result.add(entity);
+			when(aggregationResults.getMappedResults()).thenReturn(result);
+			List<MetadataInstancesDto> actual = metadataInstancesService.lienage("111");
+			assertEquals(id,actual.get(0).getId());
+		}
+	}
+	@Nested
+	class BeforeCreateOrUpdateTest{
+		private MetadataInstancesDto data;
+		private DataSourceServiceImpl dataSourceService;
+		private DataSourceDefinitionServiceImpl dataSourceDefinitionService;
+		@BeforeEach
+		void beforeEach(){
+			dataSourceService = mock(DataSourceServiceImpl.class);
+			dataSourceDefinitionService = mock(DataSourceDefinitionServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService, "dataSourceService", dataSourceService);
+			ReflectionTestUtils.setField(metadataInstancesService, "dataSourceDefinitionService", dataSourceDefinitionService);
+		}
+		@Test
+		@DisplayName("test beforeCreateOrUpdate method normal")
+		void test1(){
+			try (MockedStatic<PdkSchemaConvert> mb = Mockito
+					.mockStatic(PdkSchemaConvert.class)) {
+				TapTable tapTable = mock(TapTable.class);
+				data = new MetadataInstancesDto();
+				data.setConnectionId("CONN_65bc933f6129fe73d7858d6f");
+				data.setMetaType("collection");
+				ArrayList<Field> fields = new ArrayList<>();
+				fields.add(new Field());
+				Field field = new Field();
+				field.setId("111");
+				fields.add(field);
+				data.setFields(fields);
+				mb.when(()->PdkSchemaConvert.toPdk(data)).thenReturn(tapTable);
+				DataSourceConnectionDto connectionDto = mock(DataSourceConnectionDto.class);
+				when(connectionDto.getId()).thenReturn(mock(ObjectId.class));
+				when(dataSourceService.findById(toObjectId("65bc933f6129fe73d7858d6f"),userDetail)).thenReturn(connectionDto);
+				MetadataInstancesDto metadataInstancesDto = mock(MetadataInstancesDto.class);
+				doReturn(metadataInstancesDto).when(metadataInstancesService).findOne(any(Query.class));
+				when(metadataInstancesDto.getId()).thenReturn(mock(ObjectId.class));
+				LinkedHashMap<String, TapField> nameFiledMap = new LinkedHashMap<>();
+				TapField tapField = mock(TapField.class);
+				nameFiledMap.put("test",tapField);
+				when(tapTable.getNameFieldMap()).thenReturn(nameFiledMap);
+				when(dataSourceDefinitionService.getByDataSourceType(anyString(),any(UserDetail.class))).thenReturn(mock(DataSourceDefinitionDto.class));
+				mb.when(()->PdkSchemaConvert.fromPdk(tapTable)).thenReturn(mock(MetadataInstancesDto.class));
+				metadataInstancesService.beforeCreateOrUpdate(data,userDetail);
+			}
+		}
+	}
+	@Nested
+	class AfterFindByIdTest{
+		private MetadataInstancesDto result;
+		@Test
+		@DisplayName("test afterFindById method when result fields is empty")
+		void test1(){
+			result = mock(MetadataInstancesDto.class);
+			when(result.getFields()).thenReturn(new ArrayList<>());
+			metadataInstancesService.afterFindById(result);
+			assertEquals(0,result.getFields().size());
+		}
+		@Test
+		@DisplayName("test afterFindById method normal")
+		void test2(){
+			result = mock(MetadataInstancesDto.class);
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			field.setPrimaryKeyPosition(1);
+			field.setForeignKeyPosition(2);
+			fields.add(field);
+			when(result.getFields()).thenReturn(fields);
+			metadataInstancesService.afterFindById(result);
+			assertEquals(true,field.getPrimaryKey());
+			assertEquals(true,field.getForeignKey());
+		}
+		@Test
+		@DisplayName("test afterFindById method when key position is null")
+		void test3(){
+			result = mock(MetadataInstancesDto.class);
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			fields.add(field);
+			when(result.getFields()).thenReturn(fields);
+			metadataInstancesService.afterFindById(result);
+			assertEquals(false,field.getPrimaryKey());
+			assertEquals(false,field.getForeignKey());
+		}
+		@Test
+		@DisplayName("test afterFindById method when key position less than 0")
+		void test4(){
+			result = mock(MetadataInstancesDto.class);
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			field.setPrimaryKeyPosition(0);
+			field.setForeignKeyPosition(0);
+			fields.add(field);
+			when(result.getFields()).thenReturn(fields);
+			metadataInstancesService.afterFindById(result);
+			assertEquals(false,field.getPrimaryKey());
+			assertEquals(false,field.getForeignKey());
+		}
+	}
+	@Nested
+	class AfterFindOneTest{
+		private MetadataInstancesDto result;
+		@Test
+		@DisplayName("test afterFindOne method when result is null")
+		void test1(){
+			result = null;
+			metadataInstancesService.afterFindOne(result,userDetail);
+			verify(metadataInstancesService,never()).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+		@Test
+		@DisplayName("test afterFindOne method when metaType is database")
+		void test2(){
+			result = mock(MetadataInstancesDto.class);
+			when(result.getMetaType()).thenReturn("database");
+			when(result.getId()).thenReturn(mock(ObjectId.class));
+			metadataInstancesService.afterFindOne(result,userDetail);
+			verify(metadataInstancesService,new Times(1)).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+		@Test
+		@DisplayName("test afterFindOne method when metaType is collection")
+		void test3(){
+			result = mock(MetadataInstancesDto.class);
+			when(result.getMetaType()).thenReturn("collection");
+			when(result.getId()).thenReturn(mock(ObjectId.class));
+			metadataInstancesService.afterFindOne(result,userDetail);
+			verify(metadataInstancesService,new Times(1)).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+		@Test
+		@DisplayName("test afterFindOne method when metaType is collection and collections is not empty")
+		void test4(){
+			result = mock(MetadataInstancesDto.class);
+			when(result.getMetaType()).thenReturn("collection");
+			List<MetadataInstancesDto> collections = new ArrayList<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setOriginalName("111");
+			collections.add(metadataInstancesDto);
+			doReturn(collections).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			metadataInstancesService.afterFindOne(result,userDetail);
+			verify(metadataInstancesService,new Times(1)).findAllDto(any(Query.class),any(UserDetail.class));
+			verify(result,new Times(1)).setDatabase(anyString());
+		}
+		@Test
+		@DisplayName("test afterFindOne method when metaType is view")
+		void test5(){
+			result = mock(MetadataInstancesDto.class);
+			when(result.getMetaType()).thenReturn("view");
+			when(result.getId()).thenReturn(mock(ObjectId.class));
+			metadataInstancesService.afterFindOne(result,userDetail);
+			verify(metadataInstancesService,new Times(1)).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class AfterFindAllTest{
+		private List<MetadataInstancesDto> results;
+		private UserService userService;
+		@BeforeEach
+		void beforeEach(){
+			userService = mock(UserService.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"userService",userService);
+		}
+		@Test
+		@DisplayName("test afterFindAll method normal")
+		void test1(){
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setDatabaseId("65bc933c6129fe73d7858b40");
+			SourceDto sourceDto = new SourceDto();
+			sourceDto.setUser_id("65bc933c6129fe73d7858b41");
+			sourceDto.setUsername("username");
+			metadataInstancesDto.setSource(sourceDto);
+			metadataInstancesDto.setMetaType("table");
+			metadataInstancesDto.setDatabaseId("222");
+			metadataInstancesDto.setComment("test comment");
+			results = new ArrayList<>();
+			results.add(metadataInstancesDto);
+			List<UserDto> userDtos = new ArrayList<>();
+			UserDto userDto = new UserDto();
+			userDto.setId(new ObjectId("65bc933c6129fe73d7858b41"));
+			userDto.setUsername("username");
+			userDto.setEmail("email@163.com");
+			userDtos.add(userDto);
+			when(userService.findAll(any(Query.class))).thenReturn(userDtos);
+			List<MetadataInstancesDto> collections = new ArrayList<>();
+			MetadataInstancesDto meta = new MetadataInstancesDto();
+			meta.setId(new ObjectId("65bc933c6129fe73d7858b41"));
+			meta.setOriginalName("originalName");
+			collections.add(meta);
+			doReturn(collections).when(metadataInstancesService).findAll(any(Query.class));
+			metadataInstancesService.afterFindAll(results);
+			assertEquals("test comment",results.get(0).getComment());
+		}
+		@Test
+		@DisplayName("test afterFindAll method simple")
+		void test2(){
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setDatabaseId("65bc933c6129fe73d7858b40");
+			metadataInstancesDto.setDatabaseId("222");
+			results = new ArrayList<>();
+			results.add(metadataInstancesDto);
+			List<UserDto> userDtos = new ArrayList<>();
+			when(userService.findAll(any(Query.class))).thenReturn(userDtos);
+			List<MetadataInstancesDto> collections = new ArrayList<>();
+			MetadataInstancesDto meta = new MetadataInstancesDto();
+			meta.setId(new ObjectId("65bc933c6129fe73d7858b41"));
+			meta.setOriginalName("originalName");
+			collections.add(meta);
+			doReturn(collections).when(metadataInstancesService).findAll(any(Query.class));
+			metadataInstancesService.afterFindAll(results);
+			assertEquals("",results.get(0).getComment());
+		}
+	}
+	@Nested
+	class AfterFindTest{
+		private MetadataInstancesDto metadata;
+		@Test
+		@DisplayName("test afterFind method when metadata is null")
+		void test1(){
+			try (MockedStatic<CollectionUtils> mb = Mockito
+					.mockStatic(CollectionUtils.class)) {
+				metadata = null;
+				metadataInstancesService.afterFind(metadata);
+				verify(metadataInstancesService,new Times(1)).afterFind(metadata);
+				mb.verify(() -> CollectionUtils.isNotEmpty(anyCollection()),new Times(0));
+			}
+		}
+		@Test
+		@DisplayName("test afterFind method normal")
+		void test2(){
+			try (MockedStatic<CollectionUtils> mb = Mockito
+					.mockStatic(CollectionUtils.class)) {
+				metadata = new MetadataInstancesDto();
+				metadata.setFields(new ArrayList<>());
+				metadataInstancesService.afterFind(metadata);
+				verify(metadataInstancesService,new Times(1)).afterFind(metadata);
+				mb.verify(() -> CollectionUtils.isNotEmpty(anyCollection()),new Times(1));
+			}
+		}
+	}
+	@Nested
+	class AfterFindForListTest{
+		private List<MetadataInstancesDto> metadatas;
+		private DataSourceService dataSourceService;
+
+		@BeforeEach
+		void setUp() {
+			metadatas = new ArrayList<>();
+			dataSourceService = mock(DataSourceServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService, "dataSourceService", dataSourceService);
+		}
+		@Test
+		@DisplayName("test afterFind for List normal")
+		void test1(){
+			MetadataInstancesDto metadata = new MetadataInstancesDto();
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			field.setDeleted(false);
+			field.setIsNullable("YES");
+			fields.add(field);
+			metadata.setFields(fields);
+			SourceDto sourceDto = new SourceDto();
+			sourceDto.setId(new ObjectId("65bc933c6129fe73d7858b40"));
+			metadata.setSource(sourceDto);
+			metadata.setMetaType("table");
+			metadatas.add(metadata);
+			DataSourceConnectionDto connectionDto = new DataSourceConnectionDto();
+			connectionDto.setId(new ObjectId("65bc933c6129fe73d7858b41"));
+			when(dataSourceService.findById(any(ObjectId.class))).thenReturn(connectionDto);
+			metadataInstancesService.afterFind(metadatas);
+			assertEquals("65bc933c6129fe73d7858b41",metadatas.get(0).getSource().get_id());
+		}
+		@Test
+		@DisplayName("test afterFind for List when result fields is empty and metaType is job")
+		void test2(){
+			MetadataInstancesDto metadata = new MetadataInstancesDto();
+			List<Field> fields = new ArrayList<>();
+			metadata.setFields(fields);
+			SourceDto sourceDto = new SourceDto();
+			metadata.setSource(sourceDto);
+			metadata.setMetaType("job");
+			metadatas.add(metadata);
+			metadataInstancesService.afterFind(metadatas);
+			verify(dataSourceService,new Times(0)).findById(any(ObjectId.class));
+		}
+		@Test
+		@DisplayName("test afterFind for List when result source is null")
+		void test3(){
+			MetadataInstancesDto metadata = new MetadataInstancesDto();
+			List<Field> fields = new ArrayList<>();
+			metadata.setFields(fields);
+			metadatas.add(metadata);
+			metadataInstancesService.afterFind(metadatas);
+			verify(dataSourceService,new Times(0)).findById(any(ObjectId.class));
+		}
+		@Test
+		@DisplayName("test afterFind for List when connectionId is null")
+		void test4(){
+			try (MockedStatic<MongoUtils> mb = Mockito
+					.mockStatic(MongoUtils.class)) {
+				MetadataInstancesDto metadata = new MetadataInstancesDto();
+				List<Field> fields = new ArrayList<>();
+				Field field = new Field();
+				field.setDeleted(false);
+				field.setIsNullable("NO");
+				Field field1 = new Field();
+				field1.setDeleted(true);
+				fields.add(field);
+				fields.add(field1);
+				metadata.setFields(fields);
+				SourceDto sourceDto = new SourceDto();
+				sourceDto.setId(null);
+				sourceDto.set_id("65bc933c6129fe73d7858b40");
+				metadata.setSource(sourceDto);
+				metadata.setMetaType("table");
+				metadatas.add(metadata);
+				DataSourceConnectionDto connectionDto = new DataSourceConnectionDto();
+				connectionDto.setId(new ObjectId("65bc933c6129fe73d7858b41"));
+				when(dataSourceService.findById(any(ObjectId.class))).thenReturn(connectionDto);
+				metadataInstancesService.afterFind(metadatas);
+				assertEquals("65bc933c6129fe73d7858b40",metadatas.get(0).getSource().get_id());
+				mb.verify(() -> MongoUtils.toObjectId(anyString()),new Times(1));
+			}
+		}
+	}
+	@Nested
+	class ClassificationsTest{
+		private List<ClassificationParam> classificationParamList;
+		@BeforeEach
+		void beforeEach(){
+			classificationParamList = new ArrayList<>();
+		}
+		@Test
+		@DisplayName("test classifications method normal")
+		void test1(){
+			ClassificationParam classificationParam = new ClassificationParam();
+			classificationParam.setId("65bc933c6129fe73d7858b40");
+			List<Tag> classifications = new ArrayList<>();
+			classifications.add(mock(Tag.class));
+			classificationParam.setClassifications(classifications);
+			classificationParamList.add(classificationParam);
+			MongoTemplate template = mock(MongoTemplate.class);
+			when(metadataInstancesRepository.getMongoOperations()).thenReturn(template);
+			UpdateResult updateResult = mock(UpdateResult.class);
+			when(template.upsert(any(Query.class),any(Update.class),anyString())).thenReturn(updateResult);
+			when(updateResult.getModifiedCount()).thenReturn(1L);
+			MetadataInstancesDto metadataInstancesDto = mock(MetadataInstancesDto.class);
+			doReturn(metadataInstancesDto).when(metadataInstancesService).findById(any(ObjectId.class));
+			when(metadataInstancesDto.getMetaType()).thenReturn("database");
+			SourceDto sourceDto = mock(SourceDto.class);
+			when(metadataInstancesDto.getSource()).thenReturn(sourceDto);
+			when(sourceDto.get_id()).thenReturn("65bc933c6129fe73d7858b40");
+			Map<String, Object> actual = metadataInstancesService.classifications(classificationParamList);
+			assertEquals(1,actual.get("rows"));
+		}
+		@Test
+		@DisplayName("test classifications method when updateResult is null")
+		void test2(){
+			ClassificationParam classificationParam = new ClassificationParam();
+			classificationParam.setId("65bc933c6129fe73d7858b40");
+			List<Tag> classifications = new ArrayList<>();
+			classifications.add(mock(Tag.class));
+			classificationParam.setClassifications(classifications);
+			classificationParamList.add(classificationParam);
+			MongoTemplate template = mock(MongoTemplate.class);
+			when(metadataInstancesRepository.getMongoOperations()).thenReturn(template);
+			when(template.upsert(any(Query.class),any(Update.class),anyString())).thenReturn(null);
+			Map<String, Object> actual = metadataInstancesService.classifications(classificationParamList);
+			assertEquals(0,actual.get("rows"));
+			verify(metadataInstancesService,new Times(0)).findById(any(ObjectId.class));
+		}
+		@Test
+		@DisplayName("test classifications method when collectionName is empty")
+		void test3(){
+			ClassificationParam classificationParam = new ClassificationParam();
+			classificationParam.setId("65bc933c6129fe73d7858b40");
+			List<Tag> classifications = new ArrayList<>();
+			classifications.add(mock(Tag.class));
+			classificationParam.setClassifications(classifications);
+			classificationParamList.add(classificationParam);
+			MongoTemplate template = mock(MongoTemplate.class);
+			when(metadataInstancesRepository.getMongoOperations()).thenReturn(template);
+			UpdateResult updateResult = mock(UpdateResult.class);
+			when(template.upsert(any(Query.class),any(Update.class),anyString())).thenReturn(updateResult);
+			when(updateResult.getModifiedCount()).thenReturn(1L);
+			MetadataInstancesDto metadataInstancesDto = mock(MetadataInstancesDto.class);
+			doReturn(metadataInstancesDto).when(metadataInstancesService).findById(any(ObjectId.class));
+			when(metadataInstancesDto.getMetaType()).thenReturn("table");
+			SourceDto sourceDto = mock(SourceDto.class);
+			when(metadataInstancesDto.getSource()).thenReturn(sourceDto);
+			when(sourceDto.get_id()).thenReturn("65bc933c6129fe73d7858b40");
+			Map<String, Object> actual = metadataInstancesService.classifications(classificationParamList);
+			assertEquals(1,actual.get("rows"));
+			verify(template,never()).updateFirst(any(Query.class),any(Update.class),anyString());
+		}
+
+	}
+	@Nested
+	class BeforeUpdateByIdTest{
+		private ObjectId id;
+		private MetadataInstancesDto data;
+		@Test
+		@DisplayName("test beforeUpdateById method when id is null")
+		void test1(){
+			id = null;
+			data = mock(MetadataInstancesDto.class);
+			metadataInstancesService.beforeUpdateById(id,data);
+			verify(metadataInstancesService,never()).findById(any(ObjectId.class));
+		}
+		@Test
+		@DisplayName("test beforeUpdateById method normal")
+		void test2(){
+			id = new ObjectId("65bc933c6129fe73d7858b40");
+			data = new MetadataInstancesDto();
+			List<Field> fieldsAfter = new ArrayList<>();
+			fieldsAfter.add(mock(Field.class));
+			data.setFieldsAfter(fieldsAfter);
+			MetadataInstancesDto metadata = new MetadataInstancesDto();
+			doReturn(metadata).when(metadataInstancesService).findById(id);
+			metadataInstancesService.beforeUpdateById(id,data);
+			verify(metadataInstancesService,new Times(1)).findById(any(ObjectId.class));
+		}
+	}
+	@Nested
+	class CompareHistoryTest{
+		private ObjectId id;
+		private int historyVersion;
+		@Test
+		@DisplayName("test compareHistory method when metadata is null")
+		void test1(){
+			id = mock(ObjectId.class);
+			historyVersion = 1;
+			assertThrows(BizException.class,()->metadataInstancesService.compareHistory(id,historyVersion));
+		}
+		@Test
+		@DisplayName("test compareHistory method when histories is empty")
+		void test2(){
+			id = mock(ObjectId.class);
+			historyVersion = 1;
+			MetadataInstancesDto metadata = new MetadataInstancesDto();
+			doReturn(metadata).when(metadataInstancesService).findById(id);
+			assertThrows(BizException.class,()->metadataInstancesService.compareHistory(id,historyVersion));
+		}
+		@Test
+		@DisplayName("test compareHistory method normal")
+		void test3(){
+			try (MockedStatic<MetadataUtil> mb = Mockito
+					.mockStatic(MetadataUtil.class)) {
+				id = mock(ObjectId.class);
+				historyVersion = 1;
+				MetadataInstancesDto metadata = new MetadataInstancesDto();
+				List<MetadataInstancesDto> histories = new ArrayList<>();
+				MetadataInstancesDto history = new MetadataInstancesDto();
+				histories.add(history);
+				history.setVersion(1);
+				metadata.setHistories(histories);
+				doReturn(metadata).when(metadataInstancesService).findById(id);
+				metadataInstancesService.compareHistory(id,historyVersion);
+				mb.verify(() -> MetadataUtil.compare(any(MetadataInstancesDto.class),any(MetadataInstancesDto.class)),new Times(1));
+			}
+		}
+		@Test
+		@DisplayName("test compareHistory method when secondMeta is null")
+		void test4(){
+			try (MockedStatic<MetadataUtil> mb = Mockito
+					.mockStatic(MetadataUtil.class)) {
+				id = mock(ObjectId.class);
+				historyVersion = 2;
+				MetadataInstancesDto metadata = new MetadataInstancesDto();
+				List<MetadataInstancesDto> histories = new ArrayList<>();
+				MetadataInstancesDto history = new MetadataInstancesDto();
+				histories.add(history);
+				history.setVersion(1);
+				metadata.setHistories(histories);
+				doReturn(metadata).when(metadataInstancesService).findById(id);
+				assertThrows(BizException.class,()->metadataInstancesService.compareHistory(id,historyVersion));
+				mb.verify(() -> MetadataUtil.compare(any(MetadataInstancesDto.class),any(MetadataInstancesDto.class)),new Times(0));
+			}
+		}
+
+	}
+	@Nested
+	class TableConnectionTest{
+		@Test
+		@DisplayName("test tableConnection method normal")
+		void test1(){
+			String name = "test";
+			List<MetadataInstancesDto> metaArr = new ArrayList<>();
+			MetadataInstancesDto meta = new MetadataInstancesDto();
+			SourceDto sourceDto = new SourceDto();
+			sourceDto.setId(mock(ObjectId.class));
+			meta.setSource(sourceDto);
+			metaArr.add(meta);
+			doReturn(metaArr).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			List<MetadataInstancesDto> actual = metadataInstancesService.tableConnection(name, userDetail);
+			assertEquals(metaArr,actual);
+		}
+		@Test
+		@DisplayName("test tableConnection method when source is null")
+		void test2(){
+			String name = "test";
+			List<MetadataInstancesDto> metaArr = new ArrayList<>();
+			MetadataInstancesDto meta = new MetadataInstancesDto();
+			metaArr.add(meta);
+			doReturn(metaArr).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			List<MetadataInstancesDto> actual = metadataInstancesService.tableConnection(name, userDetail);
+			assertEquals(metaArr,actual);
+		}
+		@Test
+		@DisplayName("test tableConnection method when metaArr is null")
+		void test3(){
+			String name = "test";
+			doReturn(new ArrayList<>()).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			List<MetadataInstancesDto> actual = metadataInstancesService.tableConnection(name, userDetail);
+			assertEquals(null,actual);
+		}
+	}
+	@Nested
+	class OriginalDataTest{
+		private String isTarget;
+		private String qualified_name;
+		@Test
+		void testOriginalDataNormal(){
+			metadataInstancesService.originalData(isTarget,qualified_name,userDetail);
+			verify(metadataInstancesService).findByQualifiedName(qualified_name,userDetail);
+		}
+	}
+	@Nested
+	class FindBySourceIdAndTableNameTest{
+		@Test
+		void testFindBySourceIdAndTableNameNormal(){
+			String sourceId = "111";
+			String tableName = "test";
+			String taskId = "222";
+			metadataInstancesService.findBySourceIdAndTableName(sourceId,tableName,taskId,userDetail);
+			verify(metadataInstancesService).findOne(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class FindSourceSchemaBySourceIdTest{
+		private String sourceId;
+		private List<String> tableNames;
+		private String fields;
+		@Test
+		@DisplayName("test findSourceSchemaBySourceId method when tableName is empty and fields is null")
+		void test1(){
+			sourceId = "111";
+			tableNames = new ArrayList<>();
+			fields = null;
+			metadataInstancesService.findSourceSchemaBySourceId(sourceId,tableNames,userDetail,fields);
+			verify(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+		@Test
+		@DisplayName("test findSourceSchemaBySourceId method normal")
+		void test2(){
+			sourceId = "111";
+			tableNames = new ArrayList<>();
+			tableNames.add("test");
+			fields = "test_field";
+			metadataInstancesService.findSourceSchemaBySourceId(sourceId,tableNames,userDetail,fields);
+			verify(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class FindBySourceIdAndTableNameListTest{
+		@Test
+		void testFindBySourceIdAndTableNameListNormal(){
+			String sourceId = "111";
+			List<String> tableNames = new ArrayList<>();
+			tableNames.add("test");
+			String taskId = "222";
+			metadataInstancesService.findBySourceIdAndTableNameList(sourceId,tableNames,userDetail,taskId);
+			verify(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class FindBySourceIdAndTableNameListNeTaskIdTest{
+		@Test
+		void testFindBySourceIdAndTableNameListNeTaskIdNormal(){
+			String sourceId = "111";
+			List<String> tableNames = new ArrayList<>();
+			tableNames.add("test");
+			metadataInstancesService.findBySourceIdAndTableNameListNeTaskId(sourceId,tableNames,userDetail);
+			verify(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class FindEntityBySourceIdAndTableNameListTest{
+		@Test
+		void testfindEntityBySourceIdAndTableNameListNormal(){
+			String sourceId = "111";
+			List<String> tableNames = new ArrayList<>();
+			tableNames.add("test");
+			String taskId = "222";
+			metadataInstancesService.findEntityBySourceIdAndTableNameList(sourceId,tableNames,userDetail,taskId);
+			verify(metadataInstancesService).findAll(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class FindByQualifiedNameListTest{
+		@Test
+		void testFindByQualifiedNameListNormal(){
+			List<String> qualifiedNames = new ArrayList<>();
+			qualifiedNames.add("test");
+			String taskId = "111";
+			metadataInstancesService.findByQualifiedNameList(qualifiedNames,taskId);
+			verify(metadataInstancesService).findAll(any(Query.class));
+		}
+	}
+	@Nested
+	class FindByQualifiedNameNotDeleteTest{
+		@Test
+		void testFindByQualifiedNameNotDeleteNJormal(){
+			List<String> qualifiedNames = new ArrayList<>();
+			qualifiedNames.add("test");
+			String excludeFiled = "field";
+			metadataInstancesService.findByQualifiedNameNotDelete(qualifiedNames,userDetail,excludeFiled);
+			verify(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+
+	}
+	@Nested
+	class FindDatabaseSchemeNoHistoryTest{
+		@Test
+		void testFindDatabaseSchemeNoHistoryNormal(){
+			List<String> databaseIds = new ArrayList<>();
+			databaseIds.add("111");
+			metadataInstancesService.findDatabaseSchemeNoHistory(databaseIds,userDetail);
+			verify(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+
+	}
+	@Nested
+	class BulkSaveTest{
+		private List<MetadataInstancesDto> metadataInstancesDtos;
+		private MetadataInstancesDto dataSourceMetadataInstance;
+		private DataSourceConnectionDto dataSourceConnectionDto;
+		private DAG.Options options;
+		private Map<String, MetadataInstancesEntity> existsMetadataInstances;
+		@BeforeEach
+		void beforeEach(){
+			metadataInstancesDtos = new ArrayList<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setQualifiedName("test_qualifiedName");
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			field.setOriginalFieldName("originName");
+			fields.add(field);
+			metadataInstancesDto.setFields(fields);
+			metadataInstancesDto.setOriginalName("table1");
+			metadataInstancesDto.setMetaType("table");
+			metadataInstancesDtos.add(metadataInstancesDto);
+			dataSourceMetadataInstance = new MetadataInstancesDto();
+			dataSourceConnectionDto = new DataSourceConnectionDto();
+			options = new DAG.Options();
+			existsMetadataInstances = new HashMap<>();
+			MetadataInstancesEntity entity = new MetadataInstancesEntity();
+			List<Field> fields1 = new ArrayList<>();
+			Field field1 = new Field();
+			entity.setVersion(1);
+			field1.setOriginalFieldName("originName");
+			field1.setSource("manual");
+			field1.setDataType("String");
+			field1.setPrecision(1);
+			field1.setScale(1);
+			fields1.add(field1);
+			entity.setFields(fields1);
+			existsMetadataInstances.put("test_qualifiedName",entity);
+		}
+		@Test
+		@DisplayName("test bulkSave method when existsMetadataInstances contains qualifiedName and fieldsNameTransform is toUpperCase")
+		void test1(){
+			options.setRollback("table");
+			options.setFieldsNameTransform("toUpperCase");
+			options.setRollbackTable("table1");
+			BulkOperations bulkOperations = mock(BulkOperations.class);
+			when(metadataInstancesRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED)).thenReturn(bulkOperations);
+			when(bulkOperations.execute()).thenReturn(mock(BulkWriteResult.class));
+			metadataInstancesService.bulkSave(metadataInstancesDtos,dataSourceMetadataInstance,dataSourceConnectionDto,options,userDetail,existsMetadataInstances);
+			assertEquals("ORIGINNAME",metadataInstancesDtos.get(0).getFields().get(0).getFieldName());
+		}
+		@Test
+		@DisplayName("test bulkSave method when existsMetadataInstances contains qualifiedName and fieldsNameTransform is toLowerCase")
+		void test2(){
+			options.setRollback("table");
+			options.setFieldsNameTransform("toLowerCase");
+			options.setRollbackTable("table1");
+			BulkOperations bulkOperations = mock(BulkOperations.class);
+			when(metadataInstancesRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED)).thenReturn(bulkOperations);
+			when(bulkOperations.execute()).thenReturn(mock(BulkWriteResult.class));
+			metadataInstancesService.bulkSave(metadataInstancesDtos,dataSourceMetadataInstance,dataSourceConnectionDto,options,userDetail,existsMetadataInstances);
+			assertEquals("originname",metadataInstancesDtos.get(0).getFields().get(0).getFieldName());
+		}
+		@Test
+		@DisplayName("test bulkSave method when existsMetadataInstances contains qualifiedName")
+		void test3(){
+			options.setRollback("table");
+			options.setFieldsNameTransform("testTransform");
+			options.setRollbackTable("table1");
+			BulkOperations bulkOperations = mock(BulkOperations.class);
+			when(metadataInstancesRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED)).thenReturn(bulkOperations);
+			when(bulkOperations.execute()).thenReturn(mock(BulkWriteResult.class));
+			metadataInstancesService.bulkSave(metadataInstancesDtos,dataSourceMetadataInstance,dataSourceConnectionDto,options,userDetail,existsMetadataInstances);
+			assertEquals("originName",metadataInstancesDtos.get(0).getFields().get(0).getFieldName());
+			verify(bulkOperations).updateOne(any(Query.class),any(Update.class));
+		}
+		@Test
+		@DisplayName("test bulkSave method when existsMetadataInstances not contains qualifiedName and database type is vika")
+		void test4(){
+			existsMetadataInstances.clear();
+			dataSourceConnectionDto.setId(mock(ObjectId.class));
+			dataSourceConnectionDto.setDatabase_type("vika");
+			BulkOperations bulkOperations = mock(BulkOperations.class);
+			when(metadataInstancesRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED)).thenReturn(bulkOperations);
+			when(bulkOperations.execute()).thenReturn(mock(BulkWriteResult.class));
+			when(metadataInstancesRepository.buildUpdateSet(any(MetadataInstancesEntity.class),any(UserDetail.class))).thenReturn(mock(Update.class));
+			metadataInstancesService.bulkSave(metadataInstancesDtos,dataSourceMetadataInstance,dataSourceConnectionDto,options,userDetail,existsMetadataInstances);
+			verify(bulkOperations).upsert(any(Query.class),any(Update.class));
+		}
+		@Test
+		@DisplayName("test bulkSave method when existsMetadataInstances not contains qualifiedName and database type is qingflow")
+		void test5(){
+			existsMetadataInstances.clear();
+			dataSourceConnectionDto.setId(mock(ObjectId.class));
+			dataSourceConnectionDto.setDatabase_type("qingflow");
+			BulkOperations bulkOperations = mock(BulkOperations.class);
+			when(metadataInstancesRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED)).thenReturn(bulkOperations);
+			when(bulkOperations.execute()).thenReturn(mock(BulkWriteResult.class));
+			when(metadataInstancesRepository.buildUpdateSet(any(MetadataInstancesEntity.class),any(UserDetail.class))).thenReturn(mock(Update.class));
+			metadataInstancesService.bulkSave(metadataInstancesDtos,dataSourceMetadataInstance,dataSourceConnectionDto,options,userDetail,existsMetadataInstances);
+			verify(bulkOperations).upsert(any(Query.class),any(Update.class));
+		}
+		@Test
+		@DisplayName("test bulkSave method when existsMetadataInstances contains qualifiedName and fieldsNameTransform is blank")
+		void test6(){
+			options.setRollback("table");
+			options.setRollbackTable("table1");
+			BulkOperations bulkOperations = mock(BulkOperations.class);
+			when(metadataInstancesRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED)).thenReturn(bulkOperations);
+			when(bulkOperations.execute()).thenReturn(mock(BulkWriteResult.class));
+			metadataInstancesService.bulkSave(metadataInstancesDtos,dataSourceMetadataInstance,dataSourceConnectionDto,options,userDetail,existsMetadataInstances);
+			assertEquals("originName",metadataInstancesDtos.get(0).getFields().get(0).getFieldName());
+		}
+	}
+	@Nested
+	class BulkSaveTest2{
+		private List<MetadataInstancesDto> insertMetaDataDtos;
+		private Map<String, MetadataInstancesDto> updateMetaMap;
+		private boolean saveHistory;
+		private String taskId;
+		private String uuid;
+		private MetaDataHistoryService metaDataHistoryService;
+		@BeforeEach
+		void beforeEach(){
+			metaDataHistoryService = mock(MetaDataHistoryService.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"metaDataHistoryService",metaDataHistoryService);
+			BulkOperations bulkOperations = mock(BulkOperations.class);
+			when(metadataInstancesRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED)).thenReturn(bulkOperations);
+			when(bulkOperations.execute()).thenReturn(mock(BulkWriteResult.class));
+		}
+		@Test
+		@DisplayName("test bulkSave method when insertMetaDataDtos is null and updateMetaMap is not null")
+		void test1(){
+			uuid = "111";
+			saveHistory = false;
+			updateMetaMap = new HashMap<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setQualifiedName("qualifiedName");
+			updateMetaMap.put("test_updateMap",metadataInstancesDto);
+			List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+			MetadataInstancesDto meta = new MetadataInstancesDto();
+			meta.setId(mock(ObjectId.class));
+			doReturn(metadataInstancesDtos).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			metadataInstancesService.bulkSave(insertMetaDataDtos,updateMetaMap,userDetail,saveHistory,taskId,uuid);
+			verify(metadataInstancesService).deleteAll(any(Query.class),any(UserDetail.class));
+		}
+		@Test
+		@DisplayName("test bulkSave method when insertMetaDataDtos is not empty and saveHistory is true")
+		void test2(){
+			uuid = "111";
+			taskId = "222";
+			saveHistory = true;
+			insertMetaDataDtos = new ArrayList<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setQualifiedName("qualifiedName_222");
+			metadataInstancesDto.setLastUpdate(1713846744L);
+			metadataInstancesDto.setSource(mock(SourceDto.class));
+			insertMetaDataDtos.add(metadataInstancesDto);
+			MetadataInstancesDto meta = new MetadataInstancesDto();
+			meta.setId(mock(ObjectId.class));
+			meta.setTransformUuid("333");
+			doReturn(meta).when(metadataInstancesService).findOne(any(Query.class));
+			metadataInstancesService.bulkSave(insertMetaDataDtos,updateMetaMap,userDetail,saveHistory,taskId,uuid);
+			verify(metadataInstancesService).qualifiedNameLinkLogic(anyList(),any(UserDetail.class),anyString());
+		}
+		@Test
+		@DisplayName("test bulkSave method when write is false")
+		void test3(){
+			uuid = "111";
+			taskId = "222";
+			saveHistory = true;
+			int actual = metadataInstancesService.bulkSave(insertMetaDataDtos, updateMetaMap, userDetail, saveHistory, taskId, uuid);
+			assertEquals(0,actual);
+		}
+		@Test
+		@DisplayName("test bulkSave method when updateMetaMap is not empty and saveHistory is true")
+		void test4(){
+			uuid = "111";
+			taskId = "222";
+			saveHistory = true;
+			updateMetaMap = new HashMap<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setQualifiedName("qualifiedName_222");
+			ObjectId id = new ObjectId("65bc933f6129fe73d7858d6f");
+			updateMetaMap.put(id.toHexString(),metadataInstancesDto);
+			List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+			MetadataInstancesDto meta = new MetadataInstancesDto();
+			meta.setId(id);
+			metadataInstancesDtos.add(meta);
+			doReturn(metadataInstancesDtos).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			metadataInstancesService.bulkSave(insertMetaDataDtos,updateMetaMap,userDetail,saveHistory,taskId,uuid);
+			verify(metadataInstancesService).qualifiedNameLinkLogic(anyList(),any(UserDetail.class),anyString());
+		}
+	}
+	@Nested
+	class BulkUpsetByWhereTest{
+		private List<MetadataInstancesDto> metadataInstancesDtos;
+		private BulkOperations bulkOperations;
+		@BeforeEach
+		void beforeEach(){
+			metadataInstancesDtos = new ArrayList<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setQualifiedName("test_qualifiedName");
+			metadataInstancesDtos.add(metadataInstancesDto);
+			bulkOperations = mock(BulkOperations.class);
+			when(metadataInstancesRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED)).thenReturn(bulkOperations);
+			BulkWriteResult execute = mock(BulkWriteResult.class);
+			when(bulkOperations.execute()).thenReturn(execute);
+			when(execute.getModifiedCount()).thenReturn(1);
+			when(execute.getInsertedCount()).thenReturn(1);
+		}
+		@Test
+		@DisplayName("test bulkUpsetByWhere method when num mod 1000 not equals 0")
+		void test1(){
+			Pair<Integer, Integer> actual = metadataInstancesService.bulkUpsetByWhere(metadataInstancesDtos, userDetail);
+			verify(bulkOperations).execute();
+			assertEquals(1,actual.getLeft());
+			assertEquals(1,actual.getRight());
+		}
+		@Test
+		@DisplayName("test bulkUpsetByWhere method when num mod 1000 equals 0")
+		void test2(){
+			for (int i=1;i<1000;i++){
+				MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+				metadataInstancesDto.setQualifiedName("test_qualifiedName");
+				metadataInstancesDtos.add(metadataInstancesDto);
+			}
+			Pair<Integer, Integer> actual = metadataInstancesService.bulkUpsetByWhere(metadataInstancesDtos, userDetail);
+			verify(bulkOperations,new Times(2)).execute();
+			assertEquals(2,actual.getLeft());
+			assertEquals(2,actual.getRight());
+		}
+	}
+	@Nested
+	class TablesTest{
+		@Test
+		void testTablesNormal(){
+			MongoTemplate mongoTemplate = mock(MongoTemplate.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"mongoTemplate",mongoTemplate);
+			String connectId = "111";
+			String sourceType = "mysql";
+			List<MetadataInstancesEntity> list = new ArrayList<>();
+			MetadataInstancesEntity entity = new MetadataInstancesEntity();
+			entity.setOriginalName("originalName");
+			list.add(entity);
+			when(mongoTemplate.find(any(Query.class),any(Class.class))).thenReturn(list);
+			List<String> actual = metadataInstancesService.tables(connectId, sourceType);
+			assertEquals("originalName",actual.get(0));
+		}
+	}
+	@Nested
+	class TableValuesTest{
+		private String connectId;
+		private String sourceType;
+		private MongoTemplate mongoTemplate;
+		@BeforeEach
+		void beforeEach(){
+			mongoTemplate = mock(MongoTemplate.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"mongoTemplate",mongoTemplate);
+			connectId = "111";
+			sourceType = "mysql";
+		}
+		@Test
+		@DisplayName("test tableValues method when list is empty")
+		void test1(){
+			when(mongoTemplate.find(any(Query.class),any(Class.class))).thenReturn(new ArrayList());
+			List<Map<String, String>> actual = metadataInstancesService.tableValues(connectId, sourceType);
+			assertEquals(0,actual.size());
+		}
+		@Test
+		@DisplayName("test tableValues method normal")
+		void test2(){
+			List<MetadataInstancesEntity> list = new ArrayList();
+			MetadataInstancesEntity entity = new MetadataInstancesEntity();
+			entity.setOriginalName("originalName");
+			entity.setId(new ObjectId("65bc933f6129fe73d7858d6f"));
+			list.add(entity);
+			when(mongoTemplate.find(any(Query.class),any(Class.class))).thenReturn(list);
+			List<Map<String, String>> actual = metadataInstancesService.tableValues(connectId, sourceType);
+			assertEquals("originalName",actual.get(0).get("tableName"));
+			assertEquals("65bc933f6129fe73d7858d6f",actual.get(0).get("tableId"));
+			assertEquals("",actual.get(0).get("tableComment"));
+		}
+	}
+	@Nested
+	class PageTablesTest{
+		private String connectId;
+		private String sourceType;
+		private String regex;
+		private int skip;
+		private int limit;
+		private MongoTemplate mongoTemplate;
+		@BeforeEach
+		void beforeEach(){
+			mongoTemplate = mock(MongoTemplate.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"mongoTemplate",mongoTemplate);
+			connectId = "111";
+			sourceType = "mysql";
+		}
+		@Test
+		@DisplayName("test pageTables method when totals greater than 0")
+		void test1(){
+			regex = "tableName";
+			limit = 1;
+			when(mongoTemplate.count(any(Query.class),any(Class.class))).thenReturn(1L);
+			AggregationResults<Map> aggregate = mock(AggregationResults.class);
+			when(mongoTemplate.aggregate(any(Aggregation.class),anyString(),any(Class.class))).thenReturn(aggregate);
+			List<Map> list = new ArrayList<>();
+			Map map = new HashMap();
+			map.put("test","map");
+			list.add(map);
+			when(aggregate.getMappedResults()).thenReturn(list);
+			Page<Map<String, Object>> actual = metadataInstancesService.pageTables(connectId, sourceType, regex, skip, limit);
+			assertEquals(map,actual.getItems().get(0));
+		}
+		@Test
+		@DisplayName("test pageTables method when totals equals 0")
+		void test2(){
+			regex = "tableName";
+			limit = 1;
+			when(mongoTemplate.count(any(Query.class),any(Class.class))).thenReturn(0L);
+			Page<Map<String, Object>> actual = metadataInstancesService.pageTables(connectId, sourceType, regex, skip, limit);
+			assertEquals(0,actual.getItems().size());
+		}
+		@Test
+		@DisplayName("test pageTables method when limit equals 0")
+		void test3(){
+			limit = 0;
+			when(mongoTemplate.count(any(Query.class),any(Class.class))).thenReturn(1L);
+			AggregationResults<Map> aggregate = mock(AggregationResults.class);
+			when(mongoTemplate.aggregate(any(Aggregation.class),anyString(),any(Class.class))).thenReturn(aggregate);
+			List<Map> list = new ArrayList<>();
+			Map map = new HashMap();
+			map.put("test","map");
+			list.add(map);
+			when(aggregate.getMappedResults()).thenReturn(list);
+			Page<Map<String, Object>> actual = metadataInstancesService.pageTables(connectId, sourceType, regex, skip, limit);
+			assertEquals(map,actual.getItems().get(0));
+		}
+	}
+	@Nested
+	class TableSupportInspectTest{
+		@Test
+		@DisplayName("test tableSupportInspect method when fields is empty")
+		void test1(){
+			String connectId = "111";
+			String tableName = "tableName";
+			MetadataInstancesDto metadataInstancesDtos = new MetadataInstancesDto();
+			doReturn(metadataInstancesDtos).when(metadataInstancesService).findOne(any(Query.class));
+			TableSupportInspectVo actual = metadataInstancesService.tableSupportInspect(connectId, tableName);
+			assertEquals("tableName",actual.getTableName());
+			assertEquals(false,actual.getSupportInspect());
+		}
+		@Test
+		@DisplayName("test tableSupportInspect method normal")
+		void testTableSupportInspectNormal(){
+			String connectId = "111";
+			String tableName = "tableName";
+			MetadataInstancesDto metadataInstancesDtos = new MetadataInstancesDto();
+			List<Field> list = new ArrayList<>();
+			Field field = new Field();
+			field.setPrimaryKeyPosition(1);
+			list.add(field);
+			metadataInstancesDtos.setFields(list);
+			doReturn(metadataInstancesDtos).when(metadataInstancesService).findOne(any(Query.class));
+			TableSupportInspectVo actual = metadataInstancesService.tableSupportInspect(connectId, tableName);
+			assertEquals("tableName",actual.getTableName());
+			assertEquals(true,actual.getSupportInspect());
+		}
+	}
+	@Nested
+	class TablesSupportInspectTest{
+		private TablesSupportInspectParam tablesSupportInspectParam;
+		@BeforeEach
+		void beforeEach(){
+			tablesSupportInspectParam = new TablesSupportInspectParam();
+			tablesSupportInspectParam.setConnectionId("111");
+			List<String> tableNames = new ArrayList<>();
+			tableNames.add("tableName");
+			tablesSupportInspectParam.setTableNames(tableNames);
+		}
+		@Test
+		@DisplayName("test tablesSupportInspect method when fields is empty")
+		void test1(){
+			List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setOriginalName("originalName");
+			metadataInstancesDtos.add(metadataInstancesDto);
+			doReturn(metadataInstancesDtos).when(metadataInstancesService).findAll(any(Query.class));
+			List<TableSupportInspectVo> actual = metadataInstancesService.tablesSupportInspect(tablesSupportInspectParam);
+			assertEquals("originalName",actual.get(0).getTableName());
+			assertEquals(false,actual.get(0).getSupportInspect());
+		}
+		@Test
+		@DisplayName("test tablesSupportInspect method normal")
+		void testTableSupportInspectNormal(){
+			List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			List<Field> list = new ArrayList<>();
+			Field field = new Field();
+			field.setPrimaryKeyPosition(1);
+			list.add(field);
+			metadataInstancesDto.setFields(list);
+			metadataInstancesDto.setOriginalName("originalName");
+			metadataInstancesDtos.add(metadataInstancesDto);
+			doReturn(metadataInstancesDtos).when(metadataInstancesService).findAll(any(Query.class));
+			List<TableSupportInspectVo> actual = metadataInstancesService.tablesSupportInspect(tablesSupportInspectParam);
+			assertEquals("originalName",actual.get(0).getTableName());
+			assertEquals(true,actual.get(0).getSupportInspect());
+		}
+	}
+	@Nested
+	class GetMetadataTest{
+		private String connectionId;
+		private String metaType;
+		private String tableName;
+		private DataSourceServiceImpl dataSourceService;
+		@BeforeEach
+		void beforeEach(){
+			connectionId = "65bc933f6129fe73d7858d6f";
+			dataSourceService = mock(DataSourceServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceService",dataSourceService);
+		}
+		@Test
+		@DisplayName("test getMetadata method when connectionDto is null")
+		void test1(){
+			when(dataSourceService.findById(toObjectId(connectionId), userDetail)).thenReturn(null);
+			Table actual = metadataInstancesService.getMetadata(connectionId, metaType, tableName, userDetail);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test getMetadata method when metedata is null")
+		void test2(){
+			metaType = "table";
+			DataSourceConnectionDto dto = new DataSourceConnectionDto();
+			dto.setId(mock(ObjectId.class));
+			when(dataSourceService.findById(toObjectId(connectionId), userDetail)).thenReturn(dto);
+			doReturn(null).when(metadataInstancesService).findOne(any(Query.class));
+			Table actual = metadataInstancesService.getMetadata(connectionId, metaType, tableName, userDetail);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test getMetadata method normal")
+		void test3(){
+			metaType = "table";
+			DataSourceConnectionDto dto = new DataSourceConnectionDto();
+			dto.setId(mock(ObjectId.class));
+			when(dataSourceService.findById(toObjectId(connectionId), userDetail)).thenReturn(dto);
+			MetadataInstancesDto meta = new MetadataInstancesDto();
+			meta.setId(mock(ObjectId.class));
+			meta.setOriginalName("origin");
+			doReturn(meta).when(metadataInstancesService).findOne(any(Query.class));
+			Table actual = metadataInstancesService.getMetadata(connectionId, metaType, tableName, userDetail);
+			assertEquals("origin",actual.getTableName());
+		}
+	}
+	@Nested
+	class GetMetadataV2Test{
+		private String connectionId;
+		private String metaType;
+		private String tableName;
+		private DataSourceServiceImpl dataSourceService;
+		private DataSourceDefinitionServiceImpl dataSourceDefinitionService;
+		@BeforeEach
+		void beforeEach(){
+			connectionId = "65bc933f6129fe73d7858d6f";
+			dataSourceService = mock(DataSourceServiceImpl.class);
+			dataSourceDefinitionService = mock(DataSourceDefinitionServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceService",dataSourceService);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceDefinitionService",dataSourceDefinitionService);
+		}
+		@Test
+		@DisplayName("test getMetadataV2 method when connectionDto is null")
+		void test1(){
+			when(dataSourceService.findById(toObjectId(connectionId), userDetail)).thenReturn(null);
+			TapTable actual = metadataInstancesService.getMetadataV2(connectionId, metaType, tableName, userDetail);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test getMetadataV2 method when metedata is null")
+		void test2(){
+			metaType = "table";
+			DataSourceConnectionDto dto = new DataSourceConnectionDto();
+			dto.setId(mock(ObjectId.class));
+			dto.setDatabase_type("mysql");
+			when(dataSourceService.findById(toObjectId(connectionId), userDetail)).thenReturn(dto);
+			when(dataSourceDefinitionService.getByDataSourceType(dto.getDatabase_type(),userDetail)).thenReturn(mock(DataSourceDefinitionDto.class));
+			doReturn(null).when(metadataInstancesService).findOne(any(Query.class));
+			TapTable actual = metadataInstancesService.getMetadataV2(connectionId, metaType, tableName, userDetail);
+			assertEquals(null,actual);
+		}
+//		@Test
+		@DisplayName("test getMetadataV2 method normal")
+		void test3(){
+			metaType = "table";
+			DataSourceConnectionDto dto = new DataSourceConnectionDto();
+			dto.setId(mock(ObjectId.class));
+			when(dataSourceService.findById(toObjectId(connectionId), userDetail)).thenReturn(dto);
+			when(dataSourceDefinitionService.getByDataSourceType(dto.getDatabase_type(),userDetail)).thenReturn(mock(DataSourceDefinitionDto.class));
+			MetadataInstancesDto meta = new MetadataInstancesDto();
+			meta.setId(mock(ObjectId.class));
+			meta.setOriginalName("origin");
+			doReturn(meta).when(metadataInstancesService).findOne(any(Query.class));
+			TapTable actual = metadataInstancesService.getMetadataV2(connectionId, metaType, tableName, userDetail);
+			assertEquals("origin",actual.getName());
+		}
+	}
+	@Nested
+	class FindOldByNodeIdTest{
+		private Filter filter;
+		@Test
+		@DisplayName("test findOldByNodeId method when where is null")
+		void test1(){
+			filter = new Filter();
+			filter.setWhere(null);
+			List<Table> actual = metadataInstancesService.findOldByNodeId(filter, userDetail);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test findOldByNodeId method when metadatas is empty")
+		void test2(){
+			filter = new Filter();
+			Where where = new Where();
+			where.put("nodeId","111");
+			filter.setWhere(where);
+			doReturn(null).when(metadataInstancesService).findByNodeId("111",null,userDetail,null);
+			List<Table> actual = metadataInstancesService.findOldByNodeId(filter, userDetail);
+			assertEquals(0,actual.size());
+		}
+		@Test
+		@DisplayName("test findOldByNodeId method normal")
+		void test3(){
+			filter = new Filter();
+			Where where = new Where();
+			where.put("nodeId","111");
+			filter.setWhere(where);
+			List<MetadataInstancesDto> metadatas = new ArrayList<>();
+			MetadataInstancesDto dto = new MetadataInstancesDto();
+			dto.setId(mock(ObjectId.class));
+			dto.setOriginalName("origin");
+			metadatas.add(dto);
+			doReturn(metadatas).when(metadataInstancesService).findByNodeId("111",null,userDetail,null);
+			List<Table> actual = metadataInstancesService.findOldByNodeId(filter, userDetail);
+			assertEquals("origin",actual.get(0).getTableName());
+		}
+	}
+	@Nested
+	class FindTableMapByNodeIdTest{
+		private Filter filter;
+		private TaskService taskService;
+		private UserService userService;
+		@BeforeEach
+		void beforeEach(){
+			filter = new Filter();
+			taskService = mock(TaskService.class);
+			userService = mock(UserService.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"taskService",taskService);
+			ReflectionTestUtils.setField(metadataInstancesService,"userService",userService);
+		}
+		@Test
+		@DisplayName("test findTableMapByNodeId method when where is null")
+		void test1(){
+			filter.setWhere(null);
+			Map<String, String> actual = metadataInstancesService.findTableMapByNodeId(filter);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test findTableMapByNodeId method normal")
+		void test2(){
+			Where where = new Where();
+			where.put("nodeId","111");
+			filter.setWhere(where);
+			TaskDto taskDto = new TaskDto();
+			taskDto.setUserId("65bc933f6129fe73d7858d6f");
+			when(taskService.findOne(any(Query.class))).thenReturn(taskDto);
+			Map<String, String> actual = metadataInstancesService.findTableMapByNodeId(filter);
+			assertEquals(0,actual.size());
+		}
+	}
+	@Nested
+	class FindKVByNodeTest{
+		private String nodeId;
+		private TaskService taskService;
+		private UserService userService;
+		@BeforeEach
+		void beforeEach(){
+			nodeId = "111";
+			taskService = mock(TaskService.class);
+			userService = mock(UserService.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"taskService",taskService);
+			ReflectionTestUtils.setField(metadataInstancesService,"userService",userService);
+		}
+		@Test
+		void testFindKVByNodeNormal(){
+			TaskDto taskDto = new TaskDto();
+			taskDto.setUserId("65bc933f6129fe73d7858d6f");
+			DAG dag = mock(DAG.class);
+			taskDto.setDag(dag);
+			taskDto.setId(mock(ObjectId.class));
+			when(dag.getNode("111")).thenReturn(mock(Node.class));
+			when(taskService.findOne(any(Query.class))).thenReturn(taskDto);
+			when(userService.loadUserById(any(ObjectId.class))).thenReturn(mock(UserDetail.class));
+			Map<String, String> actual = metadataInstancesService.findKVByNode(nodeId);
+			assertEquals(0,actual.size());
+		}
+	}
+	@Nested
+	class GetNodeMappingTest{
+		TaskDto taskDto;
+		Map<String, String> kv;
+		Node node;
+		@Test
+		@DisplayName("test getNodeMapping method when node is processorNode")
+		void test1(){
+			kv = new HashMap<>();
+			node = new MergeTableNode();
+			node.setId("111");
+			taskDto = new TaskDto();
+			taskDto.setId(mock(ObjectId.class));
+			DAG dag = mock(DAG.class);
+			taskDto.setDag(dag);
+			List<Node> predecessors = new ArrayList<>();
+			LogCollectorNode logCollectorNode = new LogCollectorNode();
+			Map<String, LogCollecotrConnConfig> connConfigs = new HashMap<>();
+			connConfigs.put("test",mock(LogCollecotrConnConfig.class));
+			logCollectorNode.setId("222");
+			logCollectorNode.setLogCollectorConnConfigs(connConfigs);
+			predecessors.add(logCollectorNode);
+			when(dag.predecessors("111")).thenReturn(predecessors);
+			List<MetadataInstancesDto> metadatas = new ArrayList<>();
+			metadatas.add(mock(MetadataInstancesDto.class));
+			doReturn(metadatas).when(metadataInstancesService).findByNodeId(anyString(),anyList(),any(UserDetail.class),any(TaskDto.class));
+			assertThrows(RuntimeException.class,()->metadataInstancesService.getNodeMapping(userDetail,taskDto,kv,node));
+		}
+	}
+	@Nested
+	class FindHeartbeatQualifiedNameByNodeIdTest{
+		private Filter filter;
+		private TaskService taskService;
+		private DataSourceServiceImpl dataSourceService;
+		@BeforeEach
+		void beforeEach(){
+			filter = new Filter();
+			taskService = mock(TaskService.class);
+			dataSourceService = mock(DataSourceServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"taskService",taskService);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceService",dataSourceService);
+		}
+		@Test
+		@DisplayName("test findHeartbeatQualifiedNameByNodeId method when where is null")
+		void test1(){
+			filter.setWhere(null);
+			String actual = metadataInstancesService.findHeartbeatQualifiedNameByNodeId(filter, userDetail);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test findHeartbeatQualifiedNameByNodeId method when nodeId is null")
+		void test2(){
+			filter = new Filter();
+			Where where = new Where();
+			where.put("nodeId",null);
+			filter.setWhere(where);
+			String actual = metadataInstancesService.findHeartbeatQualifiedNameByNodeId(filter, userDetail);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test findHeartbeatQualifiedNameByNodeId method when where is null")
+		void test3(){
+			filter = new Filter();
+			Where where = new Where();
+			where.put("nodeId","111");
+			filter.setWhere(where);
+			TaskDto task = new TaskDto();
+			DAG dag = mock(DAG.class);
+			List<Node> targets = new ArrayList<>();
+			DataNode node = new TableNode();
+			node.setConnectionId("65bc933c6129fe73d7858b41");
+			DataSourceConnectionDto dataSource = mock(DataSourceConnectionDto.class);
+			when(dataSourceService.findById(any(ObjectId.class))).thenReturn(dataSource);
+			targets.add(node);
+			when(dag.getTargets()).thenReturn(targets);
+			task.setDag(dag);
+			task.setId(new ObjectId("65bc933c6129fe73d7858b40"));
+			when(taskService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(task);
+			String actual = metadataInstancesService.findHeartbeatQualifiedNameByNodeId(filter, userDetail);
+			assertNotEquals(null,actual);
+		}
+	}
+	@Nested
+	class GetQualifiedNameByNodeIdTest{
+		private Node node;
+		private DataSourceConnectionDto dataSource;
+		private DataSourceDefinitionDto definitionDto;
+		private String taskId;
+		private DataSourceServiceImpl dataSourceService;
+		private DataSourceDefinitionServiceImpl dataSourceDefinitionService;
+		@BeforeEach
+		void beforeEach(){
+			dataSourceService = mock(DataSourceServiceImpl.class);
+			dataSourceDefinitionService = mock(DataSourceDefinitionServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceService",dataSourceService);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceDefinitionService",dataSourceDefinitionService);
+		}
+		@Test
+		@DisplayName("test getQualifiedNameByNodeId method when node is null")
+		void test1(){
+			node = null;
+			String actual = metadataInstancesService.getQualifiedNameByNodeId(node, userDetail, dataSource, definitionDto, taskId);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test getQualifiedNameByNodeId method for TableNode")
+		void test2(){
+			node = new TableNode();
+			((TableNode)node).setConnectionId("65bc933c6129fe73d7858b40");
+			((TableNode)node).setTableName("tableName");
+			DataSourceConnectionDto connectionDto = new DataSourceConnectionDto();
+			connectionDto.setId(mock(ObjectId.class));
+			connectionDto.setDatabase_type("mongodb");
+			connectionDto.setDatabase_uri("mongodb://localhost:27017");
+			when(dataSourceService.findById(any(ObjectId.class))).thenReturn(connectionDto);
+			DataSourceDefinitionDto dataSourceDefinitionDto = new DataSourceDefinitionDto();
+			when(dataSourceDefinitionService.getByDataSourceType(anyString(),any(UserDetail.class))).thenReturn(dataSourceDefinitionDto);
+			String actual = metadataInstancesService.getQualifiedNameByNodeId(node, userDetail, dataSource, definitionDto, taskId);
+			assertNotEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test getQualifiedNameByNodeId method for ProcessorNode")
+		void test3(){
+			node = new MergeTableNode();
+			String actual = metadataInstancesService.getQualifiedNameByNodeId(node, userDetail, dataSource, definitionDto, taskId);
+			assertNotEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test getQualifiedNameByNodeId method for node")
+		void test4(){
+			node = new VirtualTargetNode();
+			String actual = metadataInstancesService.getQualifiedNameByNodeId(node, userDetail, dataSource, definitionDto, taskId);
+			assertEquals(null,actual);
+		}
+	}
+	@Nested
+	class FindDatabaseNodeQualifiedNameTest{
+		private String nodeId;
+		private TaskDto taskDto;
+		private DataSourceConnectionDto dataSource;
+		private DataSourceDefinitionDto definitionDto;
+		private List<String> includes;
+		private TaskService taskService;
+		private DataSourceServiceImpl dataSourceService;
+		private DataSourceDefinitionServiceImpl dataSourceDefinitionService;
+		@BeforeEach
+		void beforeEach(){
+			taskService = mock(TaskService.class);
+			dataSourceService = mock(DataSourceServiceImpl.class);
+			dataSourceDefinitionService = mock(DataSourceDefinitionServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"taskService",taskService);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceService",dataSourceService);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceDefinitionService",dataSourceDefinitionService);
+		}
+		@Test
+		@DisplayName("test findDatabaseNodeQualifiedName method when includes is not empty")
+		void test1(){
+			nodeId = "111";
+			includes = new ArrayList<>();
+			includes.add("tableName");
+			TaskDto task = new TaskDto();
+			DAG dag = mock(DAG.class);
+			task.setDag(dag);
+			task.setId(mock(ObjectId.class));
+			when(taskService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(task);
+			Node node = new TableNode();
+			((TableNode)node).setConnectionId("65bc933c6129fe73d7858b40");
+			List<String> tableNames = new ArrayList<>();
+			tableNames.add("tableName");
+			when(dag.getNode(nodeId)).thenReturn(node);
+			DataSourceConnectionDto connectionDto = new DataSourceConnectionDto();
+			connectionDto.setId(mock(ObjectId.class));
+			connectionDto.setDatabase_type("mongodb");
+			connectionDto.setDatabase_uri("mongodb://localhost:27017");
+			when(dataSourceService.findById(any(ObjectId.class))).thenReturn(connectionDto);
+			DataSourceDefinitionDto dataSourceDefinitionDto = new DataSourceDefinitionDto();
+			when(dataSourceDefinitionService.getByDataSourceType(anyString(),any(UserDetail.class))).thenReturn(dataSourceDefinitionDto);
+			List<String> actual = metadataInstancesService.findDatabaseNodeQualifiedName(nodeId, userDetail, taskDto, dataSource, definitionDto, includes);
+			assertNotEquals(null,actual.get(0));
+		}
+		@Test
+		@DisplayName("test findDatabaseNodeQualifiedName method sources or targets not contains tableNode")
+		void test2(){
+			nodeId = "111";
+			includes = new ArrayList<>();
+			dataSource = new DataSourceConnectionDto();
+			definitionDto =new DataSourceDefinitionDto();
+			TaskDto task = new TaskDto();
+			DAG dag = mock(DAG.class);
+			task.setDag(dag);
+			task.setId(mock(ObjectId.class));
+			when(taskService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(task);
+			Node node = new DatabaseNode();
+			when(dag.getNode(nodeId)).thenReturn(node);
+			assertThrows(BizException.class,()->metadataInstancesService.findDatabaseNodeQualifiedName(nodeId,userDetail,taskDto,dataSource,definitionDto,includes));
+		}
+		@Test
+		@DisplayName("test findDatabaseNodeQualifiedName method sources contains tableNode")
+		void test3(){
+			nodeId = "111";
+			includes = new ArrayList<>();
+			dataSource = new DataSourceConnectionDto();
+			dataSource.setId(mock(ObjectId.class));
+			definitionDto =new DataSourceDefinitionDto();
+			TaskDto task = new TaskDto();
+			DAG dag = mock(DAG.class);
+			task.setDag(dag);
+			task.setId(mock(ObjectId.class));
+			when(taskService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(task);
+			Node node = new DatabaseNode();
+			List<String> tableNames = new ArrayList<>();
+			tableNames.add("table1");
+			((DatabaseNode)node).setTableNames(tableNames);
+			when(dag.getNode(nodeId)).thenReturn(node);
+			List<Node> source = new ArrayList<>();
+			source.add(node);
+			when(dag.getSources()).thenReturn(source);
+			List<String> actual = metadataInstancesService.findDatabaseNodeQualifiedName(nodeId,userDetail,taskDto,dataSource,definitionDto,includes);
+			assertNotEquals(null,actual.get(0));
+		}
+		@Test
+		@DisplayName("test findDatabaseNodeQualifiedName method targets not contains tableNode")
+		void test4(){
+			nodeId = "111";
+			includes = new ArrayList<>();
+			dataSource = new DataSourceConnectionDto();
+			dataSource.setId(mock(ObjectId.class));
+			definitionDto =new DataSourceDefinitionDto();
+			TaskDto task = new TaskDto();
+			DAG dag = mock(DAG.class);
+			task.setDag(dag);
+			task.setId(mock(ObjectId.class));
+			when(taskService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(task);
+			Node node = new DatabaseNode();
+			when(dag.getNode(nodeId)).thenReturn(node);
+			List<SyncObjects> list = new ArrayList<>();
+			SyncObjects syncObjects = new SyncObjects();
+			List<String> tableNames = new ArrayList<>();
+			tableNames.add("table1");
+			syncObjects.setObjectNames(tableNames);
+			list.add(syncObjects);
+			((DatabaseNode)node).setSyncObjects(list);
+			List<Node> target = new ArrayList<>();
+			target.add(node);
+			when(dag.getTargets()).thenReturn(target);
+			List<String> actual = metadataInstancesService.findDatabaseNodeQualifiedName(nodeId,userDetail,taskDto,dataSource,definitionDto,includes);
+			assertNotEquals(null,actual.get(0));
+		}
+	}
+	@Nested
+	class FindByNodeIdTest{
+		@Test
+		void testFindByNodeIdWithFieldNormal(){
+			String nodeId = "111";
+			metadataInstancesService.findByNodeId(nodeId,userDetail);
+			verify(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class FindByNodeIdWithFieldsTest{
+		@Test
+		void testFindByNodeIdWithFieldNormal(){
+			String nodeId = "111";
+			String taskId = "222";
+			String fields = "test";
+			metadataInstancesService.findByNodeId(nodeId,userDetail,taskId,fields);
+			verify(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class FindByTaskIdTest{
+		@Test
+		void testFindByTaskIdNormal(){
+			String taskId = "111";
+			metadataInstancesService.findByTaskId(taskId,userDetail);
+			verify(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class FindByNodeIdWithTaskDtoTest{
+		@Test
+		void testFindByNodeIdWithTaskDtoNormal(){
+			String nodeId = "111";
+			List<String> fields = new ArrayList<>();
+			TaskDto taskDto = new TaskDto();
+			doReturn(mock(Page.class)).when(metadataInstancesService).findByNodeId(nodeId, fields, userDetail, taskDto, null, null, 1, 0);
+			metadataInstancesService.findByNodeId(nodeId,fields,userDetail,taskDto);
+			verify(metadataInstancesService).findByNodeId(nodeId, fields, userDetail, taskDto, null, null, 1, 0);
+		}
+	}
+	@Nested
+	class FindByNodeIdsTest{
+		@Test
+		void testFindByNodeIdsNormal(){
+			List<String> nodeIds = new ArrayList<>();
+			String nodeId = "111";
+			nodeIds.add(nodeId);
+			List<String> fields = new ArrayList<>();
+			TaskDto taskDto = new TaskDto();
+			Page<MetadataInstancesDto> page = new Page<>();
+			List<MetadataInstancesDto> items = new ArrayList<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			items.add(metadataInstancesDto);
+			page.setItems(items);
+			doReturn(page).when(metadataInstancesService).findByNodeId(nodeId, fields, userDetail, taskDto, null, null, 1, 0);
+			Map<String, List<MetadataInstancesDto>> actual = metadataInstancesService.findByNodeIds(nodeIds, fields, userDetail, taskDto);
+			assertEquals(items,actual.get(nodeId));
+		}
+	}
+	@Nested
+	class findByNodeIdWithTableFilterTest{
+		String nodeId;
+		List<String> fields;
+		TaskDto taskDto;
+		String tableFilter;
+		String filterType;
+		int page;
+		int pageSize;
+		private TaskService taskService;
+		@BeforeEach
+		void beforeEach(){
+			taskService = mock(TaskService.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"taskService",taskService);
+		}
+		@Test
+		@DisplayName("test findByNodeId method when taskDto is null")
+		void test1(){
+			when(taskService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(null);
+			assertThrows(BizException.class,()->metadataInstancesService.findByNodeId(nodeId,fields,userDetail,taskDto,tableFilter,filterType,page,pageSize));
+		}
+//		@Test
+		@DisplayName("test findByNodeId method for MigrateProcessorNode")
+		void test2(){
+			nodeId = "111";
+			page = 1;
+			pageSize = 10;
+			tableFilter = ".*";
+			taskDto = new TaskDto();
+			taskDto.setId(mock(ObjectId.class));
+			DAG dag = mock(DAG.class);
+			taskDto.setDag(dag);
+			Node node = new MigrateFieldRenameProcessorNode();
+			when(dag.getNode(nodeId)).thenReturn(node);
+			List<MetadataInstancesDto> all = new ArrayList<>();
+			all.add(mock(MetadataInstancesDto.class));
+			doReturn(all).when(metadataInstancesService).findAll(any(Query.class));
+			metadataInstancesService.findByNodeId(nodeId,fields,userDetail,taskDto,tableFilter,filterType,page,pageSize);
+		}
+	}
+	@Nested
+	class SearchTest{
+		String type;
+		String keyword;
+		String lastId;
+		Integer pageSize;
+		@Test
+		@DisplayName("test search method for table")
+		void test1(){
+			type = "table";
+			lastId = "65bc933c6129fe73d7858b40";
+			keyword = "test";
+			pageSize = 10;
+			List<MetadataInstancesDto> metadatas = new ArrayList<>();
+			MetadataInstancesDto dto = new MetadataInstancesDto();
+			dto.setId(mock(ObjectId.class));
+			dto.setName("name");
+			dto.setOriginalName("originalName");
+			metadatas.add(dto);
+			doReturn(metadatas).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			List<Map<String, Object>> actual = metadataInstancesService.search(type, keyword, lastId, pageSize, userDetail);
+			assertEquals("name",((HashMap)actual.get(0).get("table")).get("name"));
+			assertEquals("originalName",((HashMap)actual.get(0).get("table")).get("original_name"));
+		}
+		@Test
+		@DisplayName("test search method for column")
+		void test2(){
+			type = "column";
+			keyword = "comment";
+			pageSize = 10;
+			List<MetadataInstancesDto> metadatas = new ArrayList<>();
+			MetadataInstancesDto dto = new MetadataInstancesDto();
+			dto.setId(mock(ObjectId.class));
+			dto.setName("name");
+			dto.setOriginalName("originalName");
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			field.setFieldName("fieldName");
+			field.setOriginalFieldName("origin_fieldName");
+			field.setAliasName("aliasName");
+			field.setComment("comment");
+			field.setJavaType("javaType");
+			fields.add(field);
+			dto.setFields(fields);
+			metadatas.add(dto);
+			doReturn(metadatas).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			List<Map<String, Object>> actual = metadataInstancesService.search(type, keyword, lastId, pageSize, userDetail);
+			assertEquals("name",((HashMap)actual.get(0).get("table")).get("name"));
+			assertEquals("originalName",((HashMap)actual.get(0).get("table")).get("original_name"));
+			assertEquals("fieldName",((HashMap)((List)actual.get(0).get("columns")).get(0)).get("field_name"));
+			assertEquals("origin_fieldName",((HashMap)((List)actual.get(0).get("columns")).get(0)).get("original_field_name"));
+			assertEquals("comment",((HashMap)((List)actual.get(0).get("columns")).get(0)).get("comment"));
+			assertEquals("javaType",((HashMap)((List)actual.get(0).get("columns")).get(0)).get("type"));
+		}
+	}
+	@Nested
+	class TableSearchTest{
+		String connectionId;
+		String keyword;
+		String lastId;
+		Integer pageSize;
+		@Test
+		@DisplayName("test tableSearch method normal")
+		void test1(){
+			lastId = "65bc933c6129fe73d7858b40";
+			pageSize = 10;
+			keyword = "test";
+			List<MetadataInstancesDto> metaData = new ArrayList<>();
+			MetadataInstancesDto dto = new MetadataInstancesDto();
+			dto.setId(mock(ObjectId.class));
+			dto.setName("name");
+			dto.setOriginalName("originalName");
+			dto.setComment("");
+			metaData.add(dto);
+			doReturn(metaData).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			List<MetaTableVo> actual = metadataInstancesService.tableSearch(connectionId, keyword, lastId, pageSize, userDetail);
+			assertEquals("name",actual.get(0).getName());
+			assertEquals("originalName",actual.get(0).getOriginalName());
+			assertEquals("",actual.get(0).getComment());
+		}
+		@Test
+		@DisplayName("test tableSearch method simple")
+		void test2(){
+			keyword = "test";
+			pageSize = 0;
+			List<MetadataInstancesDto> metaData = new ArrayList<>();
+			doReturn(metaData).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			List<MetaTableVo> actual = metadataInstancesService.tableSearch(connectionId, keyword, lastId, pageSize, userDetail);
+			assertEquals(null,actual);
+		}
+	}
+	@Nested
+	class CheckTableNamesTest{
+		@Test
+		@DisplayName("test checkTableNames method when metaData is null")
+		void test1(){
+			String connectionId = "111";
+			List<String> names = new ArrayList<>();
+			MetaTableCheckVo actual = metadataInstancesService.checkTableNames(connectionId, names, userDetail);
+			assertEquals(null,actual);
+		}
+		@Test
+		@DisplayName("test checkTableNames method when collect is null")
+		void test2(){
+			String connectionId = "111";
+			List<String> names = new ArrayList<>();
+			names.add("name");
+			names.add("errorTable");
+			List<MetadataInstancesDto> metaData = new ArrayList<>();
+			MetadataInstancesDto dto = new MetadataInstancesDto();
+			dto.setName("name");
+			metaData.add(dto);
+			doReturn(metaData).when(metadataInstancesService).findAllDto(any(Query.class),any(UserDetail.class));
+			MetaTableCheckVo actual = metadataInstancesService.checkTableNames(connectionId, names, userDetail);
+			assertEquals(1,actual.getExitsTables().size());
+			assertEquals(1,actual.getErrorTables().size());
+		}
+	}
+	@Nested
+	class BatchImportTest{
+		List<MetadataInstancesDto> metadataInstancesDtos;
+		boolean cover;
+		Map<String, DataSourceConnectionDto> conMap;
+		@BeforeEach
+		void beforeEach(){
+			metadataInstancesDtos = new ArrayList<>();
+			DataSourceConnectionDto connectionDto = new DataSourceConnectionDto();
+			connectionDto.setId(mock(ObjectId.class));
+			conMap = new HashMap<>();
+			conMap.put("662877df9179877be8b37074",connectionDto);
+		}
+		@Test
+		@DisplayName("test batchImport method normal")
+		void test1(){
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setQualifiedName("qualifiedName");
+			metadataInstancesDto.setId(new ObjectId("662877df9179877be8b37075"));
+			SourceDto sourceDto = new SourceDto();
+			sourceDto.setId(new ObjectId("662877df9179877be8b37074"));
+			metadataInstancesDto.setSource(sourceDto);
+			metadataInstancesDtos.add(metadataInstancesDto);
+			doReturn(metadataInstancesDto).when(metadataInstancesService).importEntity(metadataInstancesDto,userDetail);
+			Map<String, MetadataInstancesDto> actual = metadataInstancesService.batchImport(metadataInstancesDtos, userDetail, cover, conMap);
+			assertEquals(metadataInstancesDto,actual.get("662877df9179877be8b37075"));
+		}
+		@Test
+		@DisplayName("test batchImport method simple")
+		void test2(){
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setQualifiedName("qualifiedName");
+			metadataInstancesDto.setId(new ObjectId("662877df9179877be8b37075"));
+			metadataInstancesDtos.add(metadataInstancesDto);
+			doReturn(metadataInstancesDto).when(metadataInstancesService).importEntity(metadataInstancesDto,userDetail);
+			Map<String, MetadataInstancesDto> actual = metadataInstancesService.batchImport(metadataInstancesDtos, userDetail, cover, conMap);
+			assertEquals(metadataInstancesDto,actual.get("662877df9179877be8b37075"));
+		}
+		@Test
+		@DisplayName("test batchImport method when connectionId not null and connectionDto is null")
+		void test3(){
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setQualifiedName("qualifiedName");
+			metadataInstancesDto.setId(new ObjectId("662877df9179877be8b37075"));
+			SourceDto sourceDto = new SourceDto();
+			sourceDto.set_id("662877df9179877be8b37074");
+			metadataInstancesDto.setSource(sourceDto);
+			metadataInstancesDtos.add(metadataInstancesDto);
+			conMap.clear();
+			doReturn(metadataInstancesDto).when(metadataInstancesService).importEntity(metadataInstancesDto,userDetail);
+			Map<String, MetadataInstancesDto> actual = metadataInstancesService.batchImport(metadataInstancesDtos, userDetail, cover, conMap);
+			assertEquals(metadataInstancesDto,actual.get("662877df9179877be8b37075"));
+		}
+	}
+	@Nested
+	class GetTapTableTest{
+
+	}
+	@Nested
+	class GetTapTableWithNodeTest{
+
+	}
+	@Nested
+	class GetMergeNodeParentFieldTest{
+
+	}
+	@Nested
+	class GetParentNodeTest{
+
+	}
+	@Nested
+	class LinkLogicTest{
+	}
+	@Nested
+	class DeleteTaskMetadataTest{
+		@Test
+		void testDeleteTaskMetadataNormal(){
+			String taskId = "111";
+			metadataInstancesService.deleteTaskMetadata(taskId,userDetail);
+			verify(metadataInstancesService).deleteAll(any(Query.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class DataType2TapTypeTest{
+		private DataSourceDefinitionServiceImpl dataSourceDefinitionService;
+		@BeforeEach
+		void beforeEach(){
+			dataSourceDefinitionService = mock(DataSourceDefinitionServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceDefinitionService",dataSourceDefinitionService);
+		}
+		@Test
+		void testDataType2TapTypeNormal(){
+			CommonUtils.setProperty(SystemConstants.JUNIT_TEST_PROP_KEY, "true");
+			try (MockedStatic<DefaultExpressionMatchingMap> mb = Mockito
+					.mockStatic(DefaultExpressionMatchingMap.class)) {
+				try (MockedStatic<InstanceFactory> instanceFactory = Mockito
+						.mockStatic(InstanceFactory.class)) {
+					instanceFactory.when(()->InstanceFactory.instance(TableFieldTypesGenerator.class)).thenReturn(mock(TableFieldTypesGenerator.class));
+					try (MockedStatic<PdkSchemaConvert> pdkSchemaConvertMockedStatic = Mockito
+							.mockStatic(PdkSchemaConvert.class)) {
+						mb.when(() -> DefaultExpressionMatchingMap.map(anyString())).thenReturn(mock(DefaultExpressionMatchingMap.class));
+						TableFieldTypesGenerator generator = mock(TableFieldTypesGenerator.class);
+						pdkSchemaConvertMockedStatic.when(PdkSchemaConvert::getTableFieldTypesGenerator).thenReturn(generator);
+						doNothing().when(generator).autoFill(any(LinkedHashMap.class),any(DefaultExpressionMatchingMap.class));
+						DataType2TapTypeDto dto = new DataType2TapTypeDto();
+						dto.setDatabaseType("databaseType");
+						Set<String> dataTypes = new HashSet<>();
+						dataTypes.add("dataType");
+						dto.setDataTypes(dataTypes);
+						DataSourceDefinitionDto definitionDto = new DataSourceDefinitionDto();
+						definitionDto.setExpression("expression");
+						when(dataSourceDefinitionService.getByDataSourceType(anyString(), any(UserDetail.class))).thenReturn(definitionDto);
+						Map<String, TapType> actual = metadataInstancesService.dataType2TapType(dto, userDetail);
+						assertEquals(1,actual.size());
+					}
+				}
+			}
+		}
+	}
+	@Nested
+	class CheckTableExistTest{
+		@Test
+		void testCheckTableExistNormal(){
+			String connectionId = "111";
+			String tableName = "table";
+			doReturn(1L).when(metadataInstancesService).count(any(Query.class),any(UserDetail.class));
+			boolean actual = metadataInstancesService.checkTableExist(connectionId, tableName, userDetail);
+			assertEquals(true,actual);
+		}
+	}
+	@Nested
+	class CountUpdateExNumTest{
+		@Test
+		void testDeleteLogicModelNormal(){
+			String nodeId = "111";
+			metadataInstancesService.countUpdateExNum(nodeId);
+			verify(metadataInstancesService).count(any(Query.class));
+		}
+	}
+	@Nested
+	class CountTransformExNumTest{
+		@Test
+		void testCountTransformExNumNormal(){
+			String nodeId = "111";
+			metadataInstancesService.countTransformExNum(nodeId);
+			verify(metadataInstancesService).count(any(Query.class));
+		}
+	}
+	@Nested
+	class CountTotalNumTest{
+		@Test
+		void testCountTotalNumNormal(){
+			String nodeId = "111";
+			metadataInstancesService.countTotalNum(nodeId);
+			verify(metadataInstancesService).count(any(Query.class));
+		}
+	}
+	@Nested
+	class DeleteLogicModelTest{
+		@Test
+		void testDeleteLogicModelNormal(){
+			String taskId = "111";
+			String nodeId = "222";
+			metadataInstancesService.deleteLogicModel(taskId,nodeId);
+			verify(metadataInstancesService).deleteAll(any(Query.class));
+		}
+	}
+	@Nested
+	class UpdateTableDescTest{
+		private MetadataInstancesDto metadataInstances;
+		@Test
+		@DisplayName("test updateTableDesc method when metadataInstances id is empty")
+		void test1(){
+			metadataInstances = new MetadataInstancesDto();
+			assertThrows(BizException.class,()->metadataInstancesService.updateTableDesc(metadataInstances,userDetail));
+		}
+		@Test
+		@DisplayName("test updateTableDesc method normal")
+		void test2(){
+			metadataInstances = new MetadataInstancesDto();
+			metadataInstances.setId(new ObjectId());
+			metadataInstancesService.updateTableDesc(metadataInstances,userDetail);
+			verify(metadataInstancesService).update(any(Query.class),any(Update.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class UpdateTableFieldDescTest{
+		private String id;
+		private DiscoveryFieldDto discoveryFieldDto;
+		@Test
+		@DisplayName("test updateTableFieldDesc method when id is empty")
+		void test1(){
+			id = "";
+			assertThrows(BizException.class,()->metadataInstancesService.updateTableFieldDesc(id,discoveryFieldDto,userDetail));
+		}
+		@Test
+		@DisplayName("test updateTableFieldDesc method normal")
+		void test2(){
+			id = "65bc933c6129fe73d7858b40";
+			discoveryFieldDto = new DiscoveryFieldDto();
+			discoveryFieldDto.setId("222");
+			metadataInstancesService.updateTableFieldDesc(id,discoveryFieldDto,userDetail);
+			verify(metadataInstancesService).update(any(Query.class),any(Update.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class ImportEntityTest{
+		@Test
+		void testImportEntityNormal(){
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			metadataInstancesDto.setQualifiedName("qualifiedName");
+			metadataInstancesService.importEntity(metadataInstancesDto,userDetail);
+			verify(metadataInstancesService).upsert(any(Query.class),any(MetadataInstancesDto.class),any(UserDetail.class));
+		}
+	}
+	@Nested
+	class UpdateFieldCustomDescTest{
+		String qualifiedName;
+		Map<String, String> fieldCustomDescMap;
+		@Test
+		void testUpdateFieldCustomDescNormal(){
+			fieldCustomDescMap = new HashMap<>();
+			fieldCustomDescMap.put("fieldName","test_fieldName");
+			MetadataInstancesDto dto = new MetadataInstancesDto();
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			field.setFieldName("fieldName");
+			fields.add(field);
+			dto.setFields(fields);
+			doReturn(dto).when(metadataInstancesService).findOne(any(Query.class),any(UserDetail.class));
+			metadataInstancesService.updateFieldCustomDesc(qualifiedName,fieldCustomDescMap,userDetail);
+			verify(metadataInstancesService).update(any(Query.class),any(Update.class),any(UserDetail.class));
+			assertEquals("test_fieldName",field.getDescription());
+		}
+	}
+	@Nested
+	class DataTypeCheckMultipleTest{
+		private String databaseType;
+		private String dataType;
+		private DataSourceDefinitionServiceImpl dataSourceDefinitionService;
+		@BeforeEach
+		void beforeEach(){
+			dataSourceDefinitionService = mock(DataSourceDefinitionServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceDefinitionService",dataSourceDefinitionService);
+		}
+		@Test
+		@DisplayName("test dataTypeCheckMultiple method when definitionDto is null")
+		void test1(){
+			try (MockedStatic<DefaultExpressionMatchingMap> mb = Mockito
+					.mockStatic(DefaultExpressionMatchingMap.class)) {
+				mb.when(() -> DefaultExpressionMatchingMap.map(anyString())).thenReturn(mock(DefaultExpressionMatchingMap.class));
+				DataTypeCheckMultipleVo actual = metadataInstancesService.dataTypeCheckMultiple(databaseType, dataType, userDetail);
+				assertEquals(false,actual.isResult());
+				mb.verify(()->DefaultExpressionMatchingMap.map(anyString()),new Times(0));
+			}
+		}
+		@Test
+		@DisplayName("test dataTypeCheckMultiple method when exprResult is null")
+		void test2(){
+			try (MockedStatic<DefaultExpressionMatchingMap> mb = Mockito
+					.mockStatic(DefaultExpressionMatchingMap.class)) {
+				DataSourceDefinitionDto definitionDto = new DataSourceDefinitionDto();
+				definitionDto.setExpression("expression");
+				when(dataSourceDefinitionService.getByDataSourceType(databaseType,userDetail)).thenReturn(definitionDto);
+				DefaultExpressionMatchingMap map = mock(DefaultExpressionMatchingMap.class);
+				mb.when(() -> DefaultExpressionMatchingMap.map(anyString())).thenReturn(map);
+				when(map.get(anyString())).thenReturn(null);
+				DataTypeCheckMultipleVo actual = metadataInstancesService.dataTypeCheckMultiple(databaseType, dataType, userDetail);
+				assertEquals(false,actual.isResult());
+			}
+		}
+		@Test
+		@DisplayName("test dataTypeCheckMultiple method when exprResult getParams is null")
+		void test3(){
+			try (MockedStatic<DefaultExpressionMatchingMap> mb = Mockito
+					.mockStatic(DefaultExpressionMatchingMap.class)) {
+				DataSourceDefinitionDto definitionDto = new DataSourceDefinitionDto();
+				definitionDto.setExpression("expression");
+				when(dataSourceDefinitionService.getByDataSourceType(databaseType,userDetail)).thenReturn(definitionDto);
+				DefaultExpressionMatchingMap map = mock(DefaultExpressionMatchingMap.class);
+				mb.when(() -> DefaultExpressionMatchingMap.map(anyString())).thenReturn(map);
+				TypeExprResult<DataMap> exprResult = mock(TypeExprResult.class);
+				when(map.get(anyString())).thenReturn(exprResult);
+				when(exprResult.getParams()).thenReturn(null);
+				DataTypeCheckMultipleVo actual = metadataInstancesService.dataTypeCheckMultiple(databaseType, dataType, userDetail);
+				assertEquals(false,actual.isResult());
+				verify(exprResult,never()).getValue();
+			}
+		}
+		@Test
+		@DisplayName("test dataTypeCheckMultiple method for TapStringMapping")
+		void test4(){
+			try (MockedStatic<DefaultExpressionMatchingMap> mb = Mockito
+					.mockStatic(DefaultExpressionMatchingMap.class)) {
+				dataType = "data(Type";
+				DataSourceDefinitionDto definitionDto = new DataSourceDefinitionDto();
+				definitionDto.setExpression("expression");
+				when(dataSourceDefinitionService.getByDataSourceType(databaseType,userDetail)).thenReturn(definitionDto);
+				DefaultExpressionMatchingMap map = mock(DefaultExpressionMatchingMap.class);
+				mb.when(() -> DefaultExpressionMatchingMap.map(anyString())).thenReturn(map);
+				TypeExprResult<DataMap> exprResult = mock(TypeExprResult.class);
+				when(map.get(anyString())).thenReturn(exprResult);
+				when(exprResult.getParams()).thenReturn(new HashMap<>());
+				DataMap dataMap = mock(DataMap.class);
+				when(exprResult.getValue()).thenReturn(dataMap);
+				when(dataMap.get("_tapMapping")).thenReturn(mock(TapStringMapping.class));
+				DataTypeCheckMultipleVo actual = metadataInstancesService.dataTypeCheckMultiple(databaseType, dataType, userDetail);
+				assertEquals(true,actual.isResult());
+				assertEquals("data",actual.getOriginType());
+			}
+		}
+	}
+	@Nested
+	class GetTypeFilterTest{
+		@Test
+		void testGetTypeFilterNormal(){
+			String nodeId = "111";
+			List<MetadataInstancesDto> metadataInstancesDtos = new ArrayList<>();
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			field.setDataType("(test)dataType");
+			fields.add(field);
+			metadataInstancesDto.setFields(fields);
+			metadataInstancesDtos.add(metadataInstancesDto);
+			doReturn(metadataInstancesDtos).when(metadataInstancesService).findByNodeId(nodeId,null,userDetail,null);
+			Set<String> actual = metadataInstancesService.getTypeFilter(nodeId, userDetail);
+			assertEquals("dataType",actual.stream().findFirst().get());
+		}
+	}
+	@Nested
+	class MultiTransformTest{
+		private MultiPleTransformReq multiPleTransformReq;
+		private DataSourceDefinitionServiceImpl dataSourceDefinitionService;
+		@BeforeEach
+		void beforeEach(){
+			dataSourceDefinitionService = mock(DataSourceDefinitionServiceImpl.class);
+			ReflectionTestUtils.setField(metadataInstancesService,"dataSourceDefinitionService",dataSourceDefinitionService);
+		}
+		@Test
+		@DisplayName("test multiTransform method when fields is empty")
+		void test1(){
+			multiPleTransformReq = new MultiPleTransformReq();
+			MetadataInstancesDto actual = metadataInstancesService.multiTransform(multiPleTransformReq, userDetail);
+			assertEquals(null,actual.getFields());
+		}
+		@Test
+		@DisplayName("test multiTransform method when rules is empty")
+		void test2(){
+			multiPleTransformReq= new MultiPleTransformReq();
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			fields.add(field);
+			multiPleTransformReq.setFields(fields);
+			MetadataInstancesDto actual = metadataInstancesService.multiTransform(multiPleTransformReq, userDetail);
+			assertEquals(fields,actual.getFields());
+			verify(dataSourceDefinitionService,never()).getByDataSourceType(anyString(),any(UserDetail.class));
+		}
+		@Test
+		@DisplayName("test multiTransform method when dataSourceDefinitionDto is null")
+		void test3(){
+			multiPleTransformReq= new MultiPleTransformReq();
+			List<Field> fields = new ArrayList<>();
+			Field field = new Field();
+			fields.add(field);
+			multiPleTransformReq.setFields(fields);
+			List<FieldChangeRule> rules = new ArrayList<>();
+			FieldChangeRule rule = new FieldChangeRule();
+			rules.add(rule);
+			multiPleTransformReq.setRules(rules);
+			MetadataInstancesDto actual = metadataInstancesService.multiTransform(multiPleTransformReq, userDetail);
+			assertEquals(fields,actual.getFields());
+		}
+		@Test
+		@DisplayName("test multiTransform method normal")
+		void test4(){
+			try (MockedStatic<DefaultExpressionMatchingMap> mb = Mockito
+					.mockStatic(DefaultExpressionMatchingMap.class)) {
+				mb.when(()->DefaultExpressionMatchingMap.map(anyString())).thenReturn(mock(DefaultExpressionMatchingMap.class));
+				multiPleTransformReq= new MultiPleTransformReq();
+				List<Field> fields = new ArrayList<>();
+				Field field = new Field();
+				field.setDataTypeTemp("temp");
+				fields.add(field);
+				multiPleTransformReq.setFields(fields);
+				List<FieldChangeRule> rules = new ArrayList<>();
+				FieldChangeRule rule = new FieldChangeRule();
+				rules.add(rule);
+				multiPleTransformReq.setRules(rules);
+				multiPleTransformReq.setDatabaseType("databaseType");
+				multiPleTransformReq.setNodeId("111");
+				multiPleTransformReq.setQualifiedName("qualifiedName");
+				DataSourceDefinitionDto dataSourceDefinitionDto = new DataSourceDefinitionDto();
+				dataSourceDefinitionDto.setExpression("test_expression");
+				when(dataSourceDefinitionService.getByDataSourceType(anyString(),any(UserDetail.class))).thenReturn(dataSourceDefinitionDto);
+				MetadataInstancesDto actual = metadataInstancesService.multiTransform(multiPleTransformReq, userDetail);
+				assertEquals(fields,actual.getFields());
+			}
+		}
+	}
+	@Nested
+	class CheckMetadataInstancesIndexTest{
+		String cacheKeys;
+		String id;
+		@Test
+		@DisplayName("test checkMetadataInstancesIndex method when columnName is not blank")
+		void test1(){
+			id = "65bc933c6129fe73d7858b40";
+			cacheKeys = "col1";
+			MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+			List<TableIndex> indices = new ArrayList<>();
+			TableIndex index = new TableIndex();
+			List<TableIndexColumn> columns = new ArrayList<>();
+			TableIndexColumn column = new TableIndexColumn();
+			column.setColumnName("col1");
+			columns.add(column);
+			index.setColumns(columns);
+			indices.add(index);
+			metadataInstancesDto.setIndices(indices);
+			doReturn(metadataInstancesDto).when(metadataInstancesService).findById(any(ObjectId.class));
+			Boolean actual = metadataInstancesService.checkMetadataInstancesIndex(cacheKeys, id);
+			assertEquals(true,actual);
+		}
+		@Test
+		@DisplayName("test checkMetadataInstancesIndex method when columnName is blank and dIndex is null")
+		void test2(){
+			try (MockedStatic<Document> mb = Mockito
+					.mockStatic(Document.class)) {
+				mb.when(()->Document.parse(anyString())).thenReturn(null);
+				id = "65bc933c6129fe73d7858b40";
+				cacheKeys = "col1";
+				MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+				List<TableIndex> indices = new ArrayList<>();
+				TableIndex index = new TableIndex();
+				List<TableIndexColumn> columns = new ArrayList<>();
+				TableIndexColumn column = new TableIndexColumn();
+				columns.add(column);
+				index.setColumns(columns);
+				index.setIndexName("test_index");
+				indices.add(index);
+				metadataInstancesDto.setIndices(indices);
+				doReturn(metadataInstancesDto).when(metadataInstancesService).findById(any(ObjectId.class));
+				Boolean actual = metadataInstancesService.checkMetadataInstancesIndex(cacheKeys, id);
+				assertEquals(false,actual);
+			}
+		}
+		@Test
+		@DisplayName("test checkMetadataInstancesIndex method when columnName is blank")
+		void test3(){
+			try (MockedStatic<Document> mb = Mockito
+					.mockStatic(Document.class)) {
+				Document document = new Document();
+				document.put("key",mock(Document.class));
+				mb.when(()->Document.parse(anyString())).thenReturn(document);
+				id = "65bc933c6129fe73d7858b40";
+				cacheKeys = "col1";
+				MetadataInstancesDto metadataInstancesDto = new MetadataInstancesDto();
+				List<TableIndex> indices = new ArrayList<>();
+				TableIndex index = new TableIndex();
+				List<TableIndexColumn> columns = new ArrayList<>();
+				TableIndexColumn column = new TableIndexColumn();
+				columns.add(column);
+				index.setColumns(columns);
+				index.setIndexName("test_index");
+				indices.add(index);
+				metadataInstancesDto.setIndices(indices);
+				doReturn(metadataInstancesDto).when(metadataInstancesService).findById(any(ObjectId.class));
+				Boolean actual = metadataInstancesService.checkMetadataInstancesIndex(cacheKeys, id);
+				assertEquals(false,actual);
+			}
+		}
+	}
 }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
index a8ecb59d7c..910505423e 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
@@ -1,47 +1,134 @@
 package com.tapdata.tm.task.service;
 
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.extra.cglib.CglibUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mongodb.client.result.UpdateResult;
+import com.tapdata.tm.Settings.service.SettingsServiceImpl;
 import com.tapdata.tm.agent.service.AgentGroupService;
+import com.tapdata.tm.autoinspect.constants.AutoInspectConstants;
+import com.tapdata.tm.autoinspect.entity.AutoInspectProgress;
+import com.tapdata.tm.autoinspect.service.TaskAutoInspectResultsService;
+import com.tapdata.tm.base.dto.*;
 import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.base.handler.ExceptionHandler;
 import com.tapdata.tm.commons.dag.*;
-import com.tapdata.tm.commons.dag.nodes.DataParentNode;
-import com.tapdata.tm.commons.dag.nodes.TableNode;
-import com.tapdata.tm.commons.dag.process.UnwindProcessNode;
+import com.tapdata.tm.commons.dag.nodes.*;
+import com.tapdata.tm.commons.dag.process.*;
+import com.tapdata.tm.commons.dag.vo.SyncObjects;
+import com.tapdata.tm.commons.externalStorage.ExternalStorageDto;
 import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
-import com.tapdata.tm.commons.task.dto.Dag;
-import com.tapdata.tm.commons.task.dto.Message;
-import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.commons.schema.MetadataTransformerDto;
+import com.tapdata.tm.commons.task.constant.NotifyEnum;
+import com.tapdata.tm.commons.task.dto.*;
+import com.tapdata.tm.commons.task.dto.alarm.AlarmSettingVO;
+import com.tapdata.tm.commons.util.ConnHeartbeatUtils;
+import com.tapdata.tm.commons.util.JsonUtil;
+import com.tapdata.tm.config.security.SimpleGrantedAuthority;
 import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.dataflowinsight.dto.DataFlowInsightStatisticsDto;
+import com.tapdata.tm.disruptor.constants.DisruptorTopicEnum;
+import com.tapdata.tm.disruptor.service.DisruptorService;
+import com.tapdata.tm.ds.service.impl.DataSourceServiceImpl;
+import com.tapdata.tm.externalStorage.service.ExternalStorageService;
+import com.tapdata.tm.inspect.bean.Source;
+import com.tapdata.tm.inspect.bean.Stats;
+import com.tapdata.tm.inspect.dto.InspectDto;
+import com.tapdata.tm.inspect.dto.InspectResultDto;
+import com.tapdata.tm.inspect.service.InspectResultService;
+import com.tapdata.tm.inspect.service.InspectService;
+import com.tapdata.tm.lock.service.LockControlService;
+import com.tapdata.tm.message.constant.Level;
+import com.tapdata.tm.message.constant.MsgTypeEnum;
+import com.tapdata.tm.message.service.MessageServiceImpl;
+import com.tapdata.tm.messagequeue.dto.MessageQueueDto;
+import com.tapdata.tm.messagequeue.service.MessageQueueServiceImpl;
+import com.tapdata.tm.metadatainstance.service.MetaDataHistoryService;
+import com.tapdata.tm.metadatainstance.service.MetadataInstancesServiceImpl;
+import com.tapdata.tm.monitor.entity.MeasurementEntity;
+import com.tapdata.tm.monitor.param.IdParam;
+import com.tapdata.tm.monitor.service.MeasurementServiceV2;
+import com.tapdata.tm.monitoringlogs.service.MonitoringLogsService;
+import com.tapdata.tm.permissions.DataPermissionHelper;
+import com.tapdata.tm.permissions.DataPermissionHelperImpl;
+import com.tapdata.tm.permissions.IDataPermissionHelper;
+import com.tapdata.tm.permissions.constants.DataPermissionActionEnums;
+import com.tapdata.tm.permissions.constants.DataPermissionMenuEnums;
+import com.tapdata.tm.permissions.service.DataPermissionService;
+import com.tapdata.tm.schedule.service.ScheduleService;
+import com.tapdata.tm.statemachine.enums.DataFlowEvent;
+import com.tapdata.tm.statemachine.model.StateMachineResult;
+import com.tapdata.tm.statemachine.service.StateMachineService;
+import com.tapdata.tm.task.bean.*;
+import com.tapdata.tm.task.constant.TableStatusEnum;
 import com.tapdata.tm.task.dto.CheckEchoOneNodeParam;
+import com.tapdata.tm.task.entity.TaskEntity;
+import com.tapdata.tm.task.entity.TaskRecord;
+import com.tapdata.tm.task.param.LogSettingParam;
+import com.tapdata.tm.task.param.SaveShareCacheParam;
 import com.tapdata.tm.task.repository.TaskRepository;
+import com.tapdata.tm.task.service.batchin.ParseRelMigFile;
+import com.tapdata.tm.task.service.batchin.entity.ParseParam;
 import com.tapdata.tm.task.service.utils.TaskServiceUtil;
+import com.tapdata.tm.task.vo.ShareCacheDetailVo;
+import com.tapdata.tm.task.vo.ShareCacheVo;
+import com.tapdata.tm.task.vo.TaskDetailVo;
+import com.tapdata.tm.task.vo.TaskStatsDto;
+import com.tapdata.tm.transform.service.MetadataTransformerService;
+import com.tapdata.tm.user.service.UserService;
+import com.tapdata.tm.userLog.service.UserLogService;
+import com.tapdata.tm.utils.MongoUtils;
+import com.tapdata.tm.utils.SpringContextHelper;
 import com.tapdata.tm.worker.entity.Worker;
 import com.tapdata.tm.worker.service.WorkerService;
+import com.tapdata.tm.worker.vo.CalculationEngineVo;
+import io.tapdata.common.sample.request.Sample;
+import io.tapdata.exception.TapCodeException;
+import lombok.SneakyThrows;
+import org.apache.commons.lang3.StringUtils;
+import org.bson.BsonValue;
+import org.bson.Document;
 import org.bson.types.ObjectId;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.internal.verification.Times;
+import org.quartz.CronScheduleBuilder;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.aggregation.AggregationResults;
+import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.mock.web.MockMultipartFile;
 import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.URL;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.mockito.Mockito.*;
+import static org.springframework.beans.BeanUtils.copyProperties;
 
 
 class TaskServiceImplTest {
     TaskServiceImpl taskService;
     AgentGroupService agentGroupService;
     WorkerService workerService;
-
     TaskDto taskDto;
     UserDetail user;
     @BeforeEach
@@ -51,7 +138,6 @@ void init() {
         ReflectionTestUtils.setField(taskService, "agentGroupService", agentGroupService);
         workerService = mock(WorkerService.class);
         ReflectionTestUtils.setField(taskService, "workerService", workerService);
-
         taskDto = mock(TaskDto.class);
         user = mock(UserDetail.class);
     }
@@ -63,14 +149,11 @@ class ConfirmByIdTest {
         void init() {
             temp = mock(TaskDto.class);
             when(taskDto.getId()).thenReturn(mock(ObjectId.class));
-
             when(taskService.findById(any(ObjectId.class))).thenReturn(temp);
-
             doNothing().when(taskService).checkTaskInspectFlag(taskDto);
             doNothing().when(taskService).checkDagAgentConflict(taskDto,user,true);
             doNothing().when(taskService).checkDDLConflict(taskDto);
             when(taskService.confirmById(taskDto, user, true, false)).thenReturn(mock(TaskDto.class));
-
             when(taskService.confirmById(taskDto, user, true)).thenCallRealMethod();
         }
 
@@ -434,7 +517,7 @@ void testHasUnwindProcess(){
             dag.setEdges(edges);
             DAG mockDag =  DAG.build(dag);
             taskService.checkUnwindProcess(mockDag);
-            Assertions.assertEquals(tableNode.getDmlPolicy().getInsertPolicy(), DmlPolicyEnum.just_insert);
+            assertEquals(tableNode.getDmlPolicy().getInsertPolicy(), DmlPolicyEnum.just_insert);
         }
         @Test
         void testNotHasUnwindProcess(){
@@ -456,18 +539,4315 @@ void testNotHasUnwindProcess(){
             Assertions.assertNull(tableNode2.getDmlPolicy().getInsertPolicy());
         }
     }
+    @Nested
+    class dataPermissionFindByIdTest{
+        private ObjectId taskId;
+        private Field fields;
+        @Test
+        void testDataPermissionFindByIdNormal(){
+            taskId = new ObjectId("6613954dc8a36646da142da3");
+            fields = new Field();
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.findById(taskId,fields)).thenReturn(dto);
+            doCallRealMethod().when(taskService).dataPermissionFindById(taskId,fields);
+            Supplier<TaskDto> actual = taskService.dataPermissionFindById(taskId, fields);
+            assertEquals(dto,actual.get());
+        }
+    }
+    @Nested
+    class CreateTest{
+        private CustomSqlService customSqlService;
+        private DateNodeService dateNodeService;
+        private TransformSchemaService transformSchemaService;
+        @BeforeEach
+        void setUp(){
+            taskDto = mock(TaskDto.class);
+            user = mock(UserDetail.class);
+            customSqlService = mock(CustomSqlService.class);
+            dateNodeService = mock(DateNodeService.class);
+            transformSchemaService = mock(TransformSchemaService.class);
+            ReflectionTestUtils.setField(taskService, "customSqlService",customSqlService);
+            ReflectionTestUtils.setField(taskService, "dateNodeService",dateNodeService);
+            ReflectionTestUtils.setField(taskService, "transformSchemaService",transformSchemaService);
+        }
+        @Test
+        @DisplayName("test create method normal")
+        void test1(){
+            DAG dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            List<Node> nodes = new ArrayList<>();
+            nodes.add(mock(DatabaseNode.class));
+            JoinProcessorNode joinNode = mock(JoinProcessorNode.class);
+            nodes.add(joinNode);
+            when(dag.getNodes()).thenReturn(nodes);
+            when(taskDto.getName()).thenReturn("test");
+            ObjectId taskId = mock(ObjectId.class);
+            when(taskDto.getId()).thenReturn(taskId);
+            doNothing().when(taskService).checkTaskName("test", user, taskId);
+            doNothing().when(customSqlService).checkCustomSqlTask(taskDto, user);
+            doNothing().when(dateNodeService).checkTaskDateNode(taskDto, user);
+            when(taskService.save(taskDto,user)).thenReturn(taskDto);
+            doNothing().when(transformSchemaService).transformSchema(dag, user, taskId);
+            when(joinNode.getType()).thenReturn("join_processor");
+            doCallRealMethod().when(taskService).create(taskDto,user);
+            TaskDto actual = taskService.create(taskDto, user);
+            assertEquals(taskDto, actual);
+        }
+        @Test
+        @DisplayName("test create method when cron schedule error")
+        void test2(){
+            try (MockedStatic<CronScheduleBuilder> mb = Mockito
+                    .mockStatic(CronScheduleBuilder.class)) {
+                String expression = "0 */1 * * * ?";
+                mb.when(()->CronScheduleBuilder.cronSchedule(expression)).thenThrow(new RuntimeException());
+                when(taskDto.getCrontabExpression()).thenReturn(expression);
+                when(taskDto.getCrontabExpressionFlag()).thenReturn(true);
+                doCallRealMethod().when(taskService).create(taskDto,user);
+                assertThrows(BizException.class, ()->taskService.create(taskDto, user));
+            }
+        }
+    }
+    @Nested
+    class BeforeSaveTest{
+        @BeforeEach
+        void setUp() {
+            taskDto = mock(TaskDto.class);
+            user = mock(UserDetail.class);
+        }
+        @Test
+        @DisplayName("test beforeSave method when dag is null")
+        void test1(){
+            when(taskDto.getDag()).thenReturn(null);
+            doCallRealMethod().when(taskService).beforeSave(taskDto, user);
+            taskService.beforeSave(taskDto, user);
+        }
+        @Test
+        @DisplayName("test beforeSave method when nodes is null")
+        void test2(){
+            DAG dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            when(dag.getNodes()).thenReturn(new ArrayList<>());
+            doCallRealMethod().when(taskService).beforeSave(taskDto, user);
+            taskService.beforeSave(taskDto, user);
+        }
+        @Test
+        @DisplayName("test beforeSave method for merge table node")
+        void test3(){
+            try (MockedStatic<MergeTableProperties> mb = Mockito
+                    .mockStatic(MergeTableProperties.class)) {
+                MergeTableProperties properties = mock(MergeTableProperties.class);
+                mb.when(() -> MergeTableProperties.autoFillingArray(properties, false)).thenAnswer(invocations->{return null;});
+                DAG dag = mock(DAG.class);
+                when(taskDto.getDag()).thenReturn(dag);
+                List<Node> nodes = new ArrayList<>();
+                Node node = new MergeTableNode();
+                List<MergeTableProperties> list = new ArrayList<>();
+                list.add(properties);
+                ((MergeTableNode) node).setMergeProperties(list);
+                nodes.add(node);
+                when(dag.getNodes()).thenReturn(nodes);
+                doCallRealMethod().when(taskService).beforeSave(taskDto, user);
+                taskService.beforeSave(taskDto, user);
+            }
+        }
+        @Test
+        @DisplayName("test beforeSave method for cache node")
+        void test4(){
+            DAG dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            List<Node> nodes = new ArrayList<>();
+            Node node = new CacheNode();
+            nodes.add(node);
+            when(dag.getNodes()).thenReturn(nodes);
+            List<Node> source = new ArrayList<>();
+            source.add(mock(TableNode.class));
+            when(dag.getSources()).thenReturn(source);
+            doCallRealMethod().when(taskService).beforeSave(taskDto, user);
+            taskService.beforeSave(taskDto, user);
+        }
+    }
+    @Nested
+    class UpdateByIdTest{
+        private MeasurementServiceV2 measurementServiceV2;
+        private CustomSqlService customSqlService;
+        private DateNodeService dateNodeService;
+        private TaskSaveService taskSaveService;
+        private TransformSchemaAsyncService transformSchemaAsyncService;
+        @BeforeEach
+        void setUp() {
+            taskDto = mock(TaskDto.class);
+            user = mock(UserDetail.class);
+            measurementServiceV2 = mock(MeasurementServiceV2.class);
+            customSqlService = mock(CustomSqlService.class);
+            dateNodeService = mock(DateNodeService.class);
+            taskSaveService = mock(TaskSaveService.class);
+            transformSchemaAsyncService = mock(TransformSchemaAsyncService.class);
+            ReflectionTestUtils.setField(taskService, "customSqlService",customSqlService);
+            ReflectionTestUtils.setField(taskService, "dateNodeService",dateNodeService);
+            ReflectionTestUtils.setField(taskService, "measurementServiceV2", measurementServiceV2);
+            ReflectionTestUtils.setField(taskService, "taskSaveService", taskSaveService);
+            ReflectionTestUtils.setField(taskService, "transformSchemaAsyncService", transformSchemaAsyncService);
+        }
+        @Test
+        @DisplayName("test updateById method when cron expression occur error")
+        void test1(){
+            try (MockedStatic<CronScheduleBuilder> mb = Mockito
+                    .mockStatic(CronScheduleBuilder.class)) {
+                String expression = "0 */1 * * * ?";
+                mb.when(()->CronScheduleBuilder.cronSchedule(expression)).thenThrow(new RuntimeException());
+                doNothing().when(taskService).checkTaskInspectFlag(taskDto);
+                when(taskDto.getCrontabExpression()).thenReturn(expression);
+                when(taskDto.getCrontabExpressionFlag()).thenReturn(true);
+                doCallRealMethod().when(taskService).updateById(taskDto, user);
+                assertThrows(BizException.class, ()->taskService.updateById(taskDto, user));
+            }
+        }
+        @Test
+        @DisplayName("test updateById method with run tables ")
+        void test2(){
+            doNothing().when(taskService).checkTaskInspectFlag(taskDto);
+            ObjectId taskId = mock(ObjectId.class);
+            when(taskDto.getId()).thenReturn(taskId);
+            TaskDto oldTask = mock(TaskDto.class);
+            when(taskService.findById(taskId)).thenReturn(oldTask);
+            when(taskDto.getSyncType()).thenReturn("migrate");
+            when(taskDto.getType()).thenReturn("initial_sync");
+            DAG newDag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(newDag);
+            when(oldTask.getTaskRecordId()).thenReturn("111");
+            List<String> runTables = new ArrayList<>();
+            runTables.add("test_table");
+            when(measurementServiceV2.findRunTable(taskId.toHexString(),"111")).thenReturn(runTables);
+            LinkedList<DatabaseNode> newSourceNode = new LinkedList();
+            newSourceNode.add(mock(DatabaseNode.class));
+            when(newDag.getSourceNode()).thenReturn(newSourceNode);
+            doCallRealMethod().when(taskService).updateById(taskDto, user);
+            taskService.updateById(taskDto,user);
+            verify(taskService, new Times(1)).save(taskDto, user);
+        }
+        @Test
+        @DisplayName("test updateById method with empty run tables")
+        void test3(){
+            doNothing().when(taskService).checkTaskInspectFlag(taskDto);
+            ObjectId taskId = mock(ObjectId.class);
+            when(taskDto.getId()).thenReturn(taskId);
+            TaskDto oldTask = mock(TaskDto.class);
+            when(taskService.findById(taskId)).thenReturn(oldTask);
+            when(taskDto.getSyncType()).thenReturn("migrate");
+            when(taskDto.getType()).thenReturn("initial_sync");
+            DAG newDag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(newDag);
+            when(oldTask.getTaskRecordId()).thenReturn("111");
+            LinkedList<DatabaseNode> newSourceNode = new LinkedList();
+            newSourceNode.add(mock(DatabaseNode.class));
+            when(newDag.getSourceNode()).thenReturn(newSourceNode);
+            doCallRealMethod().when(taskService).updateById(taskDto, user);
+            taskService.updateById(taskDto,user);
+            verify(taskService, new Times(1)).save(taskDto, user);
+        }
+        @Test
+        @DisplayName("test updateById method when old task dto is null")
+        void test4(){
+            when(taskDto.getId()).thenReturn(null);
+            doCallRealMethod().when(taskService).updateById(taskDto, user);
+            taskService.updateById(taskDto,user);
+            verify(taskService, new Times(1)).create(taskDto, user);
+        }
+    }
+    @Nested
+    class UpdateShareCacheTaskTest{
+        private
+        String id;
+        private SaveShareCacheParam saveShareCacheParam;
+        @BeforeEach
+        void setUp() {
+            user = mock(UserDetail.class);
+            id = "65bc933e6129fe73d7858cbd";
+            saveShareCacheParam = mock(SaveShareCacheParam.class);
+        }
+        @Test
+        void testUpdateShareCacheTask(){
+            TaskDto dto = new TaskDto();
+            when(taskService.findById(MongoUtils.toObjectId(id))).thenReturn(dto);
+            when(saveShareCacheParam.getName()).thenReturn("cache_name");
+            doCallRealMethod().when(taskService).updateShareCacheTask(id,saveShareCacheParam,user);
+            TaskDto actual = taskService.updateShareCacheTask(id, saveShareCacheParam, user);
+            verify(taskService,new Times(1)).parseCacheToTaskDto(saveShareCacheParam,dto);
+            verify(taskService,new Times(1)).updateById(any(TaskDto.class),any(UserDetail.class));
+        }
+    }
+    @Nested
+    class CheckTaskNameTest{
+        private String newName ;
+        private ObjectId id;
+        @BeforeEach
+        void setUp(){
+            user = mock(UserDetail.class);
+        }
+        @Test
+        @DisplayName("test checkTaskName method for name is null")
+        void test1(){
+            newName = "";
+            doCallRealMethod().when(taskService).checkTaskName(newName,user,id);
+            assertThrows(BizException.class, ()->taskService.checkTaskName(newName,user,id));
+        }
+        @Test
+        @DisplayName("test checkTaskName method for repeat name")
+        void test2(){
+            newName = "test_name";
+            id = mock(ObjectId.class);
+            when(taskService.checkTaskNameNotError(newName,user,id)).thenReturn(true);
+            doCallRealMethod().when(taskService).checkTaskName(newName,user,id);
+            assertThrows(BizException.class, ()->taskService.checkTaskName(newName,user,id));
+        }
+    }
+    @Nested
+    class CheckTaskNameNotErrorTest{
+        @Test
+        void testNormal(){
+            user = mock(UserDetail.class);
+            String newName = "test_name";
+            ObjectId id = mock(ObjectId.class);
+            when(taskService.count(any(Query.class),any(UserDetail.class))).thenReturn(1L);
+            doCallRealMethod().when(taskService).checkTaskNameNotError(newName,user,id);
+            boolean actual = taskService.checkTaskNameNotError(newName, user, id);
+            assertEquals(true,actual);
+        }
+    }
+    @Nested
+    class ConfirmStartTest{
+        private boolean confirm;
+        private MonitoringLogsService monitoringLogsService;
+        @BeforeEach
+        void setUp(){
+            user = mock(UserDetail.class);
+            taskDto = mock(TaskDto.class);
+            monitoringLogsService = mock(MonitoringLogsService.class);
+            ReflectionTestUtils.setField(taskService,"monitoringLogsService",monitoringLogsService);
+        }
+        @Test
+        @DisplayName("test confirmStart method with exception")
+        void test1(){
+            confirm = true;
+            when(taskService.confirmById(taskDto, user, confirm)).thenReturn(taskDto);
+            doThrow(BizException.class).when(taskService).start(taskDto,user,"11");
+            doCallRealMethod().when(taskService).confirmStart(taskDto,user,confirm);
+            assertThrows(BizException.class,()->taskService.confirmStart(taskDto,user,confirm));
+        }
+        @Test
+        @DisplayName("test confirmStart method without exception")
+        void test2(){
+            confirm = true;
+            when(taskService.confirmById(taskDto, user, confirm)).thenReturn(taskDto);
+            ObjectId taskId = mock(ObjectId.class);
+            when(taskDto.getId()).thenReturn(taskId);
+            doCallRealMethod().when(taskService).confirmStart(taskDto,user,confirm);
+            taskService.confirmStart(taskDto,user,confirm);
+            verify(taskService, new Times(1)).findById(taskId,user);
+        }
+    }
+    @Nested
+    class CheckDDLConflictTest{
+        @Test
+        @DisplayName("test checkDDLConflict method when source node is not empty")
+        void test1(){
+            DAG dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            LinkedList<DatabaseNode> sourceNode = new LinkedList<>();
+            sourceNode.add(mock(DatabaseNode.class));
+            when(dag.getSourceNode()).thenReturn(sourceNode);
+            doCallRealMethod().when(taskService).checkDDLConflict(taskDto);
+            taskService.checkDDLConflict(taskDto);
+        }
+        @Test
+        @DisplayName("test checkDDLConflict method when ddl is unable")
+        void test2(){
+            DAG dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            LinkedList<DatabaseNode> sourceNode = new LinkedList<>();
+            when(dag.getSourceNode()).thenReturn(sourceNode);
+            doCallRealMethod().when(taskService).checkDDLConflict(taskDto);
+            taskService.checkDDLConflict(taskDto);
+        }
+    }
+    @Nested
+    class IsInspectPropertyChangedTest{
+        private TaskDto existedTask = mock(TaskDto.class);
+        private TaskDto newTask = mock(TaskDto.class);
+        private DatabaseNode existedSourceDataNode;
+        private DatabaseNode existedTargetDataNode;
+        private DatabaseNode newSourceDataNode;
+        private DatabaseNode newTargetDataNode;
+        @BeforeEach
+        void setUp(){
+            existedSourceDataNode = mock(DatabaseNode.class);
+            existedTargetDataNode = mock(DatabaseNode.class);
+            newSourceDataNode = mock(DatabaseNode.class);
+            newTargetDataNode = mock(DatabaseNode.class);
+            when(taskService.getSourceNode(existedTask)).thenReturn(existedSourceDataNode);
+            when(taskService.getTargetNode(existedTask)).thenReturn(existedTargetDataNode);
+            when(taskService.getSourceNode(newTask)).thenReturn(newSourceDataNode);
+            when(taskService.getTargetNode(newTask)).thenReturn(newTargetDataNode);
+        }
+        @Test
+        @DisplayName("test isInspectPropertyChanged method when existed task name not equals new task name")
+        void test1(){
+            String existedSourceName = "source_name";
+            String newSourceName = "source_name";
+            String existedTargetName = "exist_target_name";
+            String newTargetName = "new_target_name";
+            when(existedSourceDataNode.getName()).thenReturn(existedSourceName);
+            when(newSourceDataNode.getName()).thenReturn(newSourceName);
+            when(existedTargetDataNode.getName()).thenReturn(existedTargetName);
+            when(newTargetDataNode.getName()).thenReturn(newTargetName);
+            doCallRealMethod().when(taskService).isInspectPropertyChanged(existedTask,newTask);
+            Boolean actual = taskService.isInspectPropertyChanged(existedTask, newTask);
+            assertEquals(true,actual);
+        }
+        @Test
+        @DisplayName("test isInspectPropertyChanged method when existed task name equals new task name")
+        void test2(){
+            String existedSourceName = "source_name";
+            String newSourceName = "source_name";
+            String existedTargetName = "target_name";
+            String newTargetName = "target_name";
+            when(existedSourceDataNode.getName()).thenReturn(existedSourceName);
+            when(newSourceDataNode.getName()).thenReturn(newSourceName);
+            when(existedTargetDataNode.getName()).thenReturn(existedTargetName);
+            when(newTargetDataNode.getName()).thenReturn(newTargetName);
+            doCallRealMethod().when(taskService).isInspectPropertyChanged(existedTask,newTask);
+            Boolean actual = taskService.isInspectPropertyChanged(existedTask, newTask);
+            assertEquals(false,actual);
+        }
+        @Test
+        @DisplayName("test isInspectPropertyChanged method when existed source table name equals new source table name")
+        void test3(){
+            String existedSourceName = "source_name";
+            String newSourceName = "source_name";
+            String existedTargetName = "target_name";
+            String newTargetName = "target_name";
+            when(existedSourceDataNode.getName()).thenReturn(existedSourceName);
+            when(newSourceDataNode.getName()).thenReturn(newSourceName);
+            when(existedTargetDataNode.getName()).thenReturn(existedTargetName);
+            when(newTargetDataNode.getName()).thenReturn(newTargetName);
+            List<SyncObjects> newSyncObjects = new ArrayList<>();
+            SyncObjects syncObjects = mock(SyncObjects.class);
+            newSyncObjects.add(syncObjects);
+            when(newTargetDataNode.getSyncObjects()).thenReturn(newSyncObjects);
+            when(existedTargetDataNode.getSyncObjects()).thenReturn(newSyncObjects);
+            when(syncObjects.getType()).thenReturn("table");
+            List<String> objectNames1 = new ArrayList<>();
+            objectNames1.add("test1");
+            List<String> objectNames2 = new ArrayList<>();
+            objectNames2.add("test2");
+            when(syncObjects.getObjectNames()).thenReturn(objectNames1).thenReturn(objectNames2);
+            doCallRealMethod().when(taskService).isInspectPropertyChanged(existedTask,newTask);
+            Boolean actual = taskService.isInspectPropertyChanged(existedTask, newTask);
+            assertEquals(true,actual);
+        }
+    }
+    @Nested
+    class ConfirmByIdWithImportTaskTest{
+        private boolean confirm;
+        private boolean importTask;
+        private StateMachineService stateMachineService;
+        @BeforeEach
+        void setUp(){
+            confirm = true;
+            importTask = false;
+            stateMachineService = mock(StateMachineService.class);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
+        }
+        @Test
+        @DisplayName("test confirmById method when validate message is not empty")
+        void test1(){
+            DAG dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            when(taskDto.getShareCache()).thenReturn(false);
+            Map<String, List<Message>> validateMessage = new HashMap<>();
+            validateMessage.put("nodeId",new ArrayList<>());
+            when(dag.validate()).thenReturn(validateMessage);
+            doCallRealMethod().when(taskService).confirmById(taskDto,user,confirm,importTask);
+            assertThrows(BizException.class,()->taskService.confirmById(taskDto,user,confirm,importTask));
+        }
+        @Test
+        @DisplayName("test confirmById method normal")
+        void test2(){
+            DAG dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            when(taskDto.getShareCache()).thenReturn(true);
+            doCallRealMethod().when(taskService).confirmById(taskDto,user,confirm,importTask);
+            taskService.confirmById(taskDto,user,confirm,importTask);
+            verify(taskService,new Times(1)).updateById(taskDto,user);
+        }
+    }
+    @Nested
+    class CheckDagAgentConflictTest{
+        private boolean showListMsg;
+        private DataSourceServiceImpl dataSourceService;
+        private AgentGroupService agentGroupService;
+        @BeforeEach
+        void setUp(){
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = new TaskServiceImpl(repository);
+            taskDto = mock(TaskDto.class);
+            user = mock(UserDetail.class);
+            dataSourceService = mock(DataSourceServiceImpl.class);
+            agentGroupService = mock(AgentGroupService.class);
+            ReflectionTestUtils.setField(taskService,"dataSourceService",dataSourceService);
+            ReflectionTestUtils.setField(taskService,"agentGroupService",agentGroupService);
+        }
+        @Test
+        @DisplayName("test checkDagAgentConflict method when task share cache is true")
+        void test1(){
+            showListMsg = true;
+            when(taskDto.getShareCache()).thenReturn(true);
+            taskService.checkDagAgentConflict(taskDto,user,showListMsg);
+            verify(taskDto, new Times(0)).getDag();
+        }
+        @Test
+        @DisplayName("test checkDagAgentConflict method when validate message is not empty and showListMsg is true")
+        void test2(){
+            showListMsg = true;
+            when(taskDto.getShareCache()).thenReturn(false);
+            DAG dag = mock(DAG.class);
+            List<Node> nodes = new ArrayList<>();
+            nodes.add(mock(DatabaseNode.class));
+            when(dag.getNodes()).thenReturn(nodes);
+            when(taskDto.getDag()).thenReturn(dag);
+            List<DataSourceConnectionDto> dataSourceConnectionList = new ArrayList<>();
+            DataSourceConnectionDto connectionDto = mock(DataSourceConnectionDto.class);
+            when(connectionDto.getId()).thenReturn(mock(ObjectId.class));
+            when(connectionDto.getAccessNodeType()).thenReturn("MANUALLY_SPECIFIED_BY_THE_USER");
+            dataSourceConnectionList.add(connectionDto);
+            when(dataSourceService.findInfoByConnectionIdList(anyList())).thenReturn(dataSourceConnectionList);
+            List<String> connectionProcessIds = new ArrayList<>();
+            connectionProcessIds.add("111");
+            when(agentGroupService.getProcessNodeListWithGroup(connectionDto,user)).thenReturn(connectionProcessIds);
+            assertThrows(BizException.class,()->taskService.checkDagAgentConflict(taskDto,user,showListMsg));
+        }
+        @Test
+        @DisplayName("test checkDagAgentConflict method when validate message is not empty and showListMsg is false")
+        void test3(){
+            showListMsg = false;
+            when(taskDto.getShareCache()).thenReturn(false);
+            DAG dag = mock(DAG.class);
+            List<Node> nodes = new ArrayList<>();
+            nodes.add(mock(DatabaseNode.class));
+            when(dag.getNodes()).thenReturn(nodes);
+            when(taskDto.getDag()).thenReturn(dag);
+            List<DataSourceConnectionDto> dataSourceConnectionList = new ArrayList<>();
+            DataSourceConnectionDto connectionDto = mock(DataSourceConnectionDto.class);
+            when(connectionDto.getId()).thenReturn(mock(ObjectId.class));
+            when(connectionDto.getAccessNodeType()).thenReturn("MANUALLY_SPECIFIED_BY_THE_USER");
+            dataSourceConnectionList.add(connectionDto);
+            when(dataSourceService.findInfoByConnectionIdList(anyList())).thenReturn(dataSourceConnectionList);
+            List<String> connectionProcessIds = new ArrayList<>();
+            connectionProcessIds.add("111");
+            when(agentGroupService.getProcessNodeListWithGroup(connectionDto,user)).thenReturn(connectionProcessIds);
+            assertThrows(BizException.class,()->taskService.checkDagAgentConflict(taskDto,user,showListMsg));
+        }
+    }
+    @Nested
+    class RemoveTest{
+        private ObjectId id;
+        private StateMachineService stateMachineService;
+        private TaskResetLogService taskResetLogService;
+        @BeforeEach
+        void setUp(){
+            id = mock(ObjectId.class);
+            stateMachineService = mock(StateMachineService.class);
+            taskResetLogService = mock(TaskResetLogService.class);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
+            ReflectionTestUtils.setField(taskService,"taskResetLogService",taskResetLogService);
+        }
+        @Test
+        @DisplayName("test remove method when no agent")
+        void test1(){
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.checkExistById(id,user)).thenReturn(dto);
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineService.executeAboutTask(dto,DataFlowEvent.DELETE, user)).thenReturn(stateMachineResult);
+            when(stateMachineResult.isOk()).thenReturn(true);
+            when(taskService.findAgent(dto,user)).thenReturn(true);
+            doCallRealMethod().when(taskService).remove(id,user);
+            taskService.remove(id,user);
+            verify(taskService, new Times(1)).afterRemove(dto,user);
+        }
+        @Test
+        @DisplayName("test remove method when no agent is false")
+        void test2(){
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.checkExistById(id,user)).thenReturn(dto);
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineService.executeAboutTask(dto,DataFlowEvent.DELETE, user)).thenReturn(stateMachineResult);
+            when(stateMachineResult.isOk()).thenReturn(true);
+            when(taskService.findAgent(dto,user)).thenReturn(false);
+            doNothing().when(taskService).sendRenewMq(dto,user, DataSyncMq.OP_TYPE_DELETE);
+            doCallRealMethod().when(taskService).remove(id,user);
+            taskService.remove(id,user);
+            verify(taskService, new Times(0)).afterRemove(dto,user);
+        }
+    }
+    @Nested
+    class AfterRemove{
+        private TaskAutoInspectResultsService taskAutoInspectResultsService;
+        private MessageServiceImpl messageService;
+        private MetadataInstancesServiceImpl metadataInstancesService;
+        private MetaDataHistoryService historyService;
+        private TaskCollectionObjService taskCollectionObjService;
+        @BeforeEach
+        void setUp(){
+            taskAutoInspectResultsService = mock(TaskAutoInspectResultsService.class);
+            messageService = mock(MessageServiceImpl.class);
+            metadataInstancesService = mock(MetadataInstancesServiceImpl.class);
+            historyService = mock(MetaDataHistoryService.class);
+            taskCollectionObjService = mock(TaskCollectionObjService.class);
+            ReflectionTestUtils.setField(taskService,"taskAutoInspectResultsService",taskAutoInspectResultsService);
+            ReflectionTestUtils.setField(taskService,"messageService",messageService);
+            ReflectionTestUtils.setField(taskService,"metadataInstancesService",metadataInstancesService);
+            ReflectionTestUtils.setField(taskService,"historyService",historyService);
+            ReflectionTestUtils.setField(taskService,"taskCollectionObjService",taskCollectionObjService);
+        }
+        @Test
+        @DisplayName("test afterRemove method for migrate")
+        void test1(){
+            when(taskDto.getSyncType()).thenReturn("migrate");
+            when(taskDto.getId()).thenReturn(mock(ObjectId.class));
+            doCallRealMethod().when(taskService).afterRemove(taskDto,user);
+            taskService.afterRemove(taskDto,user);
+            verify(messageService, new Times(1)).addMigration(taskDto.getDeleteName(), taskDto.getId().toString(), MsgTypeEnum.DELETED, Level.WARN, user);
+        }
+        @Test
+        @DisplayName("test afterRemove method for sync")
+        void test2(){
+            when(taskDto.getSyncType()).thenReturn("sync");
+            when(taskDto.getId()).thenReturn(mock(ObjectId.class));
+            doCallRealMethod().when(taskService).afterRemove(taskDto,user);
+            taskService.afterRemove(taskDto,user);
+            verify(messageService, new Times(1)).addSync(taskDto.getDeleteName(), taskDto.getId().toString(), MsgTypeEnum.DELETED, "", Level.WARN, user);
+        }
+    }
+    @Nested
+    class DeleteShareCache{
+        private ObjectId id;
+        @Test
+        void test1(){
+            id = mock(ObjectId.class);
+            doCallRealMethod().when(taskService).deleteShareCache(id,user);
+            taskService.deleteShareCache(id,user);
+            verify(taskService).update(any(Query.class),any(Update.class));
+        }
+    }
+    @Nested
+    class CopyTest{
+        private ObjectId id;
+        private UserLogService userLogService;
+        @BeforeEach
+        void setUp(){
+            id = mock(ObjectId.class);
+            taskDto = mock(TaskDto.class);
+            userLogService = mock(UserLogService.class);
+            ReflectionTestUtils.setField(taskService,"userLogService",userLogService);
+        }
+        @Test
+        void testCopy(){
+            try (MockedStatic<SpringContextHelper> mb = Mockito
+                    .mockStatic(SpringContextHelper.class)) {
+                TaskServiceImpl service = mock(TaskServiceImpl.class);
+                mb.when(()->SpringContextHelper.getBean(TaskServiceImpl.class)).thenReturn(service);
+                id = mock(ObjectId.class);
+                when(taskService.checkExistById(id,user)).thenReturn(taskDto);
+                DAG dag = mock(DAG.class);
+                List<Node> nodes = new ArrayList<>();
+                DatabaseNode databaseNode = mock(DatabaseNode.class);
+                nodes.add(databaseNode);
+                when(databaseNode.getId()).thenReturn("111");
+                MergeTableNode mergeTableNode = mock(MergeTableNode.class);
+                nodes.add(mergeTableNode);
+                when(mergeTableNode.getId()).thenReturn("222");
+                MigrateFieldRenameProcessorNode renameProcessorNode = mock(MigrateFieldRenameProcessorNode.class);
+                nodes.add(renameProcessorNode);
+                when(renameProcessorNode.getId()).thenReturn("333");
+                when(dag.getNodes()).thenReturn(nodes);
+                when(taskDto.getDag()).thenReturn(dag);
+                List<TaskDto.SyncPoint> syncPoints = new ArrayList<>();
+                syncPoints.add(mock(TaskDto.SyncPoint.class));
+                when(taskDto.getSyncPoints()).thenReturn(syncPoints);
+                LinkedList<Edge> edges = new LinkedList<>();
+                Edge edge = mock(Edge.class);
+                edges.add(edge);
+                when(edge.getSource()).thenReturn("source");
+                when(edge.getTarget()).thenReturn("target");
+                when(dag.getEdges()).thenReturn(edges);
+                when(service.confirmById(any(TaskDto.class),any(UserDetail.class),anyBoolean())).thenReturn(taskDto);
+                doCallRealMethod().when(taskService).copy(id,user);
+                TaskDto actual = taskService.copy(id, user);
+                assertEquals(taskDto,actual);
+            }
+        }
+    }
+    @Nested
+    class RenewTest{
+        private ObjectId id;
+        private boolean system;
+        private StateMachineService stateMachineService;
+        private TaskResetLogService taskResetLogService;
+        private DisruptorService disruptorService;
+        @BeforeEach
+        void setUp(){
+            id = mock(ObjectId.class);
+            stateMachineService = mock(StateMachineService.class);
+            taskResetLogService = mock(TaskResetLogService.class);
+            disruptorService = mock(DisruptorService.class);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
+            ReflectionTestUtils.setField(taskService,"taskResetLogService",taskResetLogService);
+            ReflectionTestUtils.setField(taskService,"disruptorService",disruptorService);
+        }
+        @Test
+        @DisplayName("test renew method when no agent")
+        void test1(){
+            system = true;
+            when(taskService.checkExistById(id,user)).thenReturn(taskDto);
+            when(taskService.findAgent(taskDto,user)).thenReturn(true);
+            doCallRealMethod().when(taskService).renew(id,user,system);
+            assertThrows(BizException.class,()->taskService.renew(id,user,system));
+        }
+        @Test
+        @DisplayName("test renew method normal")
+        void test2(){
+            system = true;
+            when(taskService.checkExistById(id,user)).thenReturn(taskDto);
+            when(taskService.findAgent(taskDto,user)).thenReturn(false);
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineService.executeAboutTask(taskDto, DataFlowEvent.RENEW, user)).thenReturn(stateMachineResult);
+            when(stateMachineResult.isOk()).thenReturn(true);
+            when(taskDto.getId()).thenReturn(mock(ObjectId.class));
+            doCallRealMethod().when(taskService).renew(id,user,system);
+            taskService.renew(id,user,system);
+            verify(disruptorService,new Times(1)).sendMessage(any(DisruptorTopicEnum.class),any(TaskRecord.class));
+        }
+        @Test
+        @DisplayName("test renew method when state machine result is not ok")
+        void test3(){
+            system = true;
+            when(taskService.checkExistById(id,user)).thenReturn(taskDto);
+            when(taskService.findAgent(taskDto,user)).thenReturn(false);
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineService.executeAboutTask(taskDto, DataFlowEvent.RENEW, user)).thenReturn(stateMachineResult);
+            when(stateMachineResult.isOk()).thenReturn(false);
+            doCallRealMethod().when(taskService).renew(id,user,system);
+            assertThrows(BizException.class,()->taskService.renew(id,user,system));
+        }
+    }
+    @Nested
+    class AfterRenewTest{
+        private TaskAutoInspectResultsService taskAutoInspectResultsService;
+        private StateMachineService stateMachineService;
+        @BeforeEach
+        void setUp(){
+            taskAutoInspectResultsService = mock(TaskAutoInspectResultsService.class);
+            stateMachineService = mock(StateMachineService.class);
+            ReflectionTestUtils.setField(taskService,"taskAutoInspectResultsService",taskAutoInspectResultsService);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
 
+        }
+        @Test
+        void testAfterRenew(){
+            UpdateResult updateResult = mock(UpdateResult.class);
+            when(taskService.renewNotSendMq(taskDto,user)).thenReturn(updateResult);
+            when(updateResult.getMatchedCount()).thenReturn(1L);
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineService.executeAboutTask(taskDto, DataFlowEvent.RENEW_DEL_SUCCESS, user)).thenReturn(stateMachineResult);
+            when(stateMachineResult.isFail()).thenReturn(true);
+            doCallRealMethod().when(taskService).afterRenew(taskDto,user);
+            taskService.afterRenew(taskDto,user);
+            verify(taskAutoInspectResultsService,new Times(1)).cleanResultsByTask(taskDto);
+        }
+    }
     @Nested
-    class FindByCacheName{
-        TaskServiceImpl taskService = spy(new TaskServiceImpl(mock(TaskRepository.class)));
+    class CheckExistByIdTest{
+        private ObjectId id;
+        @BeforeEach
+        void setUp(){
+            id = mock(ObjectId.class);
+        }
         @Test
-        void test(){
-            doAnswer(invocationOnMock -> {
-                Query query = invocationOnMock.getArgument(0);
-                Assertions.assertEquals(query.getQueryObject().get("is_deleted"),false);
-                return null;
-            }).when(taskService).findOne(any(Query.class),any(UserDetail.class));
-            taskService.findByCacheName("test",mock(UserDetail.class));
+        @DisplayName("test checkExistById method when taskDto is null")
+        void test1(){
+            when(taskService.findById(id,user)).thenReturn(null);
+            doCallRealMethod().when(taskService).checkExistById(id,user);
+            assertThrows(BizException.class,()->taskService.checkExistById(id,user));
+        }
+        @Test
+        @DisplayName("test checkExistById method normal")
+        void test2(){
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.findById(id,user)).thenReturn(dto);
+            doCallRealMethod().when(taskService).checkExistById(id,user);
+            TaskDto actual = taskService.checkExistById(id, user);
+            assertEquals(dto,actual);
+        }
+    }
+    @Nested
+    class CheckExistByIdWithFieldsTest{
+        private ObjectId id;
+        private String fields;
+        @BeforeEach
+        void setUp(){
+            id = mock(ObjectId.class);
+            fields = "test_field";
+        }
+        @Test
+        @DisplayName("test checkExistById with fields method when taskDto is null")
+        void test1(){
+            when(taskService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(null);
+            doCallRealMethod().when(taskService).checkExistById(id,user,fields);
+            assertThrows(BizException.class,()->taskService.checkExistById(id,user,fields));
+        }
+        @Test
+        @DisplayName("test checkExistById with fields method normal")
+        void test2(){
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(dto);
+            doCallRealMethod().when(taskService).checkExistById(id,user,fields);
+            TaskDto actual = taskService.checkExistById(id, user, fields);
+            assertEquals(dto,actual);
+        }
+    }
+    @Nested
+    class CheckExistByIdWithoutUser{
+        private ObjectId id;
+        private String fields;
+        @BeforeEach
+        void setUp(){
+            id = mock(ObjectId.class);
+            fields = "test_field";
+        }
+        @Test
+        @DisplayName("test checkExistById without user method when taskDto is null")
+        void test1(){
+            when(taskService.findOne(any(Query.class))).thenReturn(null);
+            doCallRealMethod().when(taskService).checkExistById(id,fields);
+            assertThrows(BizException.class,()->taskService.checkExistById(id,fields));
+        }
+        @Test
+        @DisplayName("test checkExistById without user method normal")
+        void test2(){
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.findOne(any(Query.class))).thenReturn(dto);
+            doCallRealMethod().when(taskService).checkExistById(id,fields);
+            TaskDto actual = taskService.checkExistById(id,fields);
+            assertEquals(dto,actual);
+        }
+    }
+    @Nested
+    class BatchStopTest{
+        private List<ObjectId> taskIds;
+        private UserDetail user;
+        private HttpServletRequest request;
+        private HttpServletResponse response;
+        private ObjectId id;
+        private ExceptionHandler exceptionHandler;
+        @BeforeEach
+        void setUp(){
+            taskIds = new ArrayList<>();
+            id = mock(ObjectId.class);
+            taskIds.add(id);
+            request = mock(HttpServletRequest.class);
+            response = mock(HttpServletResponse.class);
+            exceptionHandler = mock(ExceptionHandler.class);
+            ReflectionTestUtils.setField(taskService,"exceptionHandler",exceptionHandler);
+        }
+        @Test
+        @DisplayName("test batchStop method with BizException")
+        void test1(){
+            BizException exception = new BizException("SystemError");
+            doThrow(exception).when(taskService).pause(id,user,false);
+            doCallRealMethod().when(taskService).batchStop(taskIds,user,request,response);
+            List<MutiResponseMessage> responseMessages = taskService.batchStop(taskIds, user, request, response);
+            assertEquals("SystemError",responseMessages.get(0).getCode());
+        }
+        @Test
+        @DisplayName("test batchStop method with exception")
+        @SneakyThrows
+        void test2(){
+            Exception e = mock(RuntimeException.class);
+            doThrow(e).when(taskService).pause(id,user,false);
+            ResponseMessage responseMessage = mock(ResponseMessage.class);
+            when(responseMessage.getCode()).thenReturn("error_code");
+            when(exceptionHandler.handlerException(e,request,response)).thenReturn(responseMessage);
+            doCallRealMethod().when(taskService).batchStop(taskIds,user,request,response);
+            List<MutiResponseMessage> responseMessages = taskService.batchStop(taskIds, user, request, response);
+            assertEquals("error_code",responseMessages.get(0).getCode());
+        }
+    }
+    @Nested
+    class BatchDeleteTest{
+        private List<ObjectId> taskIds;
+        private UserDetail user;
+        private HttpServletRequest request;
+        private HttpServletResponse response;
+        private ObjectId id;
+        private InspectService inspectService;
+        private ExceptionHandler exceptionHandler;
+        @BeforeEach
+        void setUp(){
+            taskIds = new ArrayList<>();
+            id = mock(ObjectId.class);
+            taskIds.add(id);
+            request = mock(HttpServletRequest.class);
+            response = mock(HttpServletResponse.class);
+            inspectService = mock(InspectService.class);
+            ReflectionTestUtils.setField(taskService,"inspectService",inspectService);
+            exceptionHandler = mock(ExceptionHandler.class);
+            ReflectionTestUtils.setField(taskService,"exceptionHandler",exceptionHandler);
+
+        }
+        @Test
+        @DisplayName("test batchDelete method normal")
+        void test1(){
+            doCallRealMethod().when(taskService).batchDelete(taskIds,user,request,response);
+            List<MutiResponseMessage> responseMessages = taskService.batchDelete(taskIds, user, request, response);
+            assertEquals("ok",responseMessages.get(0).getCode());
+        }
+        @Test
+        @DisplayName("test batchDelete method with BizExcception")
+        void test2(){
+            BizException exception = new BizException("Clear.Slot");
+            doThrow(exception).when(taskService).remove(id,user);
+            doCallRealMethod().when(taskService).batchDelete(taskIds,user,request,response);
+            List<MutiResponseMessage> responseMessages = taskService.batchDelete(taskIds, user, request, response);
+            assertEquals("Clear.Slot",responseMessages.get(0).getCode());
+        }
+        @Test
+        @DisplayName("test batchDelete method with exception")
+        @SneakyThrows
+        void test3(){
+            Exception e = mock(RuntimeException.class);
+            doThrow(e).when(taskService).remove(id,user);
+            ResponseMessage responseMessage = mock(ResponseMessage.class);
+            when(responseMessage.getCode()).thenReturn("error_code");
+            when(exceptionHandler.handlerException(e,request,response)).thenReturn(responseMessage);
+            doCallRealMethod().when(taskService).batchDelete(taskIds,user,request,response);
+            List<MutiResponseMessage> responseMessages = taskService.batchDelete(taskIds, user, request, response);
+            assertEquals("error_code",responseMessages.get(0).getCode());
+        }
+    }
+    @Nested
+    class BatchRenewTest{
+        private List<ObjectId> taskIds;
+        private UserDetail user;
+        private HttpServletRequest request;
+        private HttpServletResponse response;
+        private ObjectId id;
+        private ExceptionHandler exceptionHandler;
+        @BeforeEach
+        void setUp(){
+            taskIds = new ArrayList<>();
+            id = mock(ObjectId.class);
+            taskIds.add(id);
+            request = mock(HttpServletRequest.class);
+            response = mock(HttpServletResponse.class);
+            exceptionHandler = mock(ExceptionHandler.class);
+            ReflectionTestUtils.setField(taskService,"exceptionHandler",exceptionHandler);
+
+        }
+        @Test
+        @DisplayName("test batchRenew method normal")
+        void test1(){
+            doCallRealMethod().when(taskService).batchRenew(taskIds,user,request,response);
+            List<MutiResponseMessage> responseMessages = taskService.batchRenew(taskIds, user, request, response);
+            assertEquals("ok",responseMessages.get(0).getCode());
+        }
+        @Test
+        @DisplayName("test batchRenew method with BizException")
+        void test2(){
+            BizException exception = new BizException("Task.ResetAgentNotFound");
+            doThrow(exception).when(taskService).renew(id,user);
+            doCallRealMethod().when(taskService).batchRenew(taskIds,user,request,response);
+            List<MutiResponseMessage> responseMessages = taskService.batchRenew(taskIds, user, request, response);
+            assertEquals("Task.ResetAgentNotFound",responseMessages.get(0).getCode());
+        }
+        @Test
+        @DisplayName("test batchRenew method with exception")
+        @SneakyThrows
+        void test3(){
+            Exception e = mock(RuntimeException.class);
+            doThrow(e).when(taskService).renew(id,user);
+            ResponseMessage responseMessage = mock(ResponseMessage.class);
+            when(responseMessage.getCode()).thenReturn("error_code");
+            when(exceptionHandler.handlerException(e,request,response)).thenReturn(responseMessage);
+            doCallRealMethod().when(taskService).batchRenew(taskIds,user,request,response);
+            List<MutiResponseMessage> responseMessages = taskService.batchRenew(taskIds, user, request, response);
+            assertEquals("error_code",responseMessages.get(0).getCode());
+        }
+    }
+    @Nested
+    class FindTest{
+        private Filter filter;
+        private TaskRepository repository;
+        private MetadataTransformerService transformerService;
+        @BeforeEach
+        void setUp(){
+            filter = new Filter();
+            user = mock(UserDetail.class);
+            repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            when(repository.getMongoOperations()).thenReturn(mock(MongoTemplate.class));
+            new DataPermissionHelper(mock(IDataPermissionHelper.class)); //when repository.find call methods in DataPermissionHelper class this line is need
+            transformerService = mock(MetadataTransformerService.class);
+            ReflectionTestUtils.setField(taskService,"transformerService",transformerService);
+        }
+        @Test
+        @DisplayName("test find method when is agent request")
+        void test1(){
+            taskService = spy(new TaskServiceImpl(mock(TaskRepository.class)));
+            try (MockedStatic<RequestContextHolder> mb = Mockito
+                    .mockStatic(RequestContextHolder.class)) {
+                ServletRequestAttributes attributes = mock(ServletRequestAttributes.class);
+                mb.when(RequestContextHolder::currentRequestAttributes).thenReturn(attributes);
+                HttpServletRequest mockReq = mock(HttpServletRequest.class);
+                when(attributes.getRequest()).thenReturn(mockReq);
+                when(mockReq.getHeader("user-agent")).thenReturn("Java");
+                taskService.find(filter, user);
+                verify(taskService,new Times(1)).deleteNotifyEnumData(anyList());
+            }
+        }
+        @Test
+        @DisplayName("test find method when sync type not instanceof String")
+        void test2(){
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            try (MockedStatic<RequestContextHolder> mb = Mockito
+                    .mockStatic(RequestContextHolder.class)) {
+                ServletRequestAttributes attributes = mock(ServletRequestAttributes.class);
+                mb.when(RequestContextHolder::currentRequestAttributes).thenReturn(attributes);
+                HttpServletRequest mockReq = mock(HttpServletRequest.class);
+                when(attributes.getRequest()).thenReturn(mockReq);
+                when(mockReq.getHeader("user-agent")).thenReturn("");
+                filter.setWhere(null);
+                taskService.find(filter, user);
+                verify(taskService,new Times(0)).findDataCopyList(filter,user);
+                verify(taskService,new Times(0)).findDataDevList(filter,user);
+            }
+        }
+        @Test
+        @DisplayName("test find method when sync type is migrate")
+        void test3(){
+            try (MockedStatic<RequestContextHolder> mb = Mockito
+                    .mockStatic(RequestContextHolder.class)) {
+                ServletRequestAttributes attributes = mock(ServletRequestAttributes.class);
+                mb.when(RequestContextHolder::currentRequestAttributes).thenReturn(attributes);
+                HttpServletRequest mockReq = mock(HttpServletRequest.class);
+                when(attributes.getRequest()).thenReturn(mockReq);
+                when(mockReq.getHeader("user-agent")).thenReturn("");
+                Where where = mock(Where.class);
+                when(where.get("syncType")).thenReturn("migrate");
+                filter.setWhere(where);
+                taskService.find(filter, user);
+                verify(taskService,new Times(1)).findDataCopyList(filter,user);
+            }
+        }
+        @Test
+        @DisplayName("test find method when sync type is sync")
+        void test4(){
+            try (MockedStatic<RequestContextHolder> mb = Mockito
+                    .mockStatic(RequestContextHolder.class)) {
+                ServletRequestAttributes attributes = mock(ServletRequestAttributes.class);
+                mb.when(RequestContextHolder::currentRequestAttributes).thenReturn(attributes);
+                HttpServletRequest mockReq = mock(HttpServletRequest.class);
+                when(attributes.getRequest()).thenReturn(mockReq);
+                when(mockReq.getHeader("user-agent")).thenReturn("");
+                Where where = mock(Where.class);
+                when(where.get("syncType")).thenReturn("sync");
+                filter.setWhere(where);
+                when(repository.filterToQuery(filter)).thenReturn(mock(Query.class));
+                taskService.find(filter, user);
+                verify(taskService,new Times(1)).findDataDevList(filter,user);
+            }
+        }
+        @Test
+        @DisplayName("test find method when sync type is heartbeat")
+        void test5(){
+            try (MockedStatic<RequestContextHolder> mb = Mockito
+                    .mockStatic(RequestContextHolder.class)) {
+                ServletRequestAttributes attributes = mock(ServletRequestAttributes.class);
+                mb.when(RequestContextHolder::currentRequestAttributes).thenReturn(attributes);
+                HttpServletRequest mockReq = mock(HttpServletRequest.class);
+                when(attributes.getRequest()).thenReturn(mockReq);
+                when(mockReq.getHeader("user-agent")).thenReturn("");
+                Where where = mock(Where.class);
+                when(where.get("syncType")).thenReturn("connHeartbeat");
+                filter.setWhere(where);
+                when(repository.filterToQuery(filter)).thenReturn(mock(Query.class));
+                taskService.find(filter, user);
+                verify(taskService,new Times(1)).findDataDevList(filter,user);
+            }
+        }
+        @Test
+        @DisplayName("test find method")
+        void test6(){
+            try (MockedStatic<RequestContextHolder> mb = Mockito
+                    .mockStatic(RequestContextHolder.class)) {
+                MetadataTransformerService transformerService = mock(MetadataTransformerService.class);
+                ReflectionTestUtils.setField(taskService,"transformerService",transformerService);
+                ServletRequestAttributes attributes = mock(ServletRequestAttributes.class);
+                mb.when(RequestContextHolder::currentRequestAttributes).thenReturn(attributes);
+                HttpServletRequest mockReq = mock(HttpServletRequest.class);
+                when(attributes.getRequest()).thenReturn(mockReq);
+                when(mockReq.getHeader("user-agent")).thenReturn("");
+                Where where = mock(Where.class);
+                when(where.get("syncType")).thenReturn("connHeartbeat");
+                filter.setWhere(where);
+                when(repository.filterToQuery(filter)).thenReturn(mock(Query.class));
+                Page taskDtoPage = new Page<>();
+                List<TaskDto> taskDtos = new ArrayList<>();
+                TaskDto dto = new TaskDto();
+                taskDtos.add(dto);
+                taskDtoPage.setItems(taskDtos);
+                dto.setId(new ObjectId("65bc933c6129fe73d7858a8e"));
+                dto.setShareCdcStopMessage("share cdc message");
+                doReturn(taskDtoPage).when(taskService).findDataDevList(filter,user);
+                List<MetadataTransformerDto> transformerDtos = new ArrayList<>();
+                MetadataTransformerDto metadataTransformerDto = mock(MetadataTransformerDto.class);
+                when(metadataTransformerDto.getDataFlowId()).thenReturn("65bc933c6129fe73d7858a8e");
+                when(metadataTransformerDto.getStatus()).thenReturn("running");
+                transformerDtos.add(metadataTransformerDto);
+                when(transformerService.findAll(any(Query.class))).thenReturn(transformerDtos);
+                Page<TaskDto> actual = taskService.find(filter, user);
+                assertEquals("running",actual.getItems().get(0).getTransformStatus());
+            }
+        }
+    }
+    @Nested
+    class DeleteNotifyEnumDataTest{
+        private List<TaskDto> taskDtoList;
+        @BeforeEach
+        void setUp(){
+            taskDtoList = new ArrayList<>();
+        }
+        @Test
+        @DisplayName("test deleteNotifyEnumData method for empty taskDtoList")
+        void test1(){
+            doCallRealMethod().when(taskService).deleteNotifyEnumData(taskDtoList);
+            taskService.deleteNotifyEnumData(taskDtoList);
+        }
+        @Test
+        @DisplayName("test deleteNotifyEnumData method normal")
+        void test2(){
+            TaskDto dto = mock(TaskDto.class);
+            List<AlarmSettingVO> alarmSettings = new ArrayList<>();
+            AlarmSettingVO alarmSettingVO = mock(AlarmSettingVO.class);
+            alarmSettings.add(alarmSettingVO);
+            when(dto.getAlarmSettings()).thenReturn(alarmSettings);
+            taskDtoList.add(dto);
+            DAG dag = mock(DAG.class);
+            when(dto.getDag()).thenReturn(dag);
+            List<Node> nodes = new ArrayList<>();
+            Node node = mock(Node.class);
+            when(node.getAlarmSettings()).thenReturn(alarmSettings);
+            nodes.add(node);
+            when(dag.getNodes()).thenReturn(nodes);
+            List<NotifyEnum> notifyEnums = new ArrayList<>();
+            notifyEnums.add(NotifyEnum.SMS);
+            when(alarmSettingVO.getNotify()).thenReturn(notifyEnums);
+            doCallRealMethod().when(taskService).deleteNotifyEnumData(taskDtoList);
+            taskService.deleteNotifyEnumData(taskDtoList);
+        }
+    }
+    @Nested
+    class FindDataCopyListTest{
+        private Filter filter;
+        private InspectService inspectService;
+        private TaskRepository repository;
+        @BeforeEach
+        void setUp(){
+            filter = new Filter();
+            inspectService = mock(InspectService.class);
+            repository = mock(TaskRepository.class);
+            ReflectionTestUtils.setField(taskService,"inspectService",inspectService);
+            ReflectionTestUtils.setField(taskService,"repository",repository);
+            new DataPermissionHelper(mock(IDataPermissionHelper.class)); //when repository.find call methods in DataPermissionHelper class this line is need
+        }
+        @Test
+        void testFindDataCopyList(){
+            Where where = new Where();
+            where.put("inspectResult","agreement");
+            filter.setWhere(where);
+            when(taskService.parseOrToCriteria(where)).thenReturn(mock(Criteria.class));
+            MongoTemplate template = mock(MongoTemplate.class);
+            when(repository.getMongoOperations()).thenReturn(template);
+            List<TaskEntity> taskEntityList = new ArrayList<>();
+            TaskEntity task = new TaskEntity();
+            ObjectId id = mock(ObjectId.class);
+            task.setId(id);
+            taskEntityList.add(task);
+            when(template.find(any(Query.class),any(Class.class))).thenReturn(taskEntityList);
+            List<InspectDto> inspectDtoList = new ArrayList<>();
+            InspectDto inspectDto = new InspectDto();
+            String flowId = "111";
+            when(id.toHexString()).thenReturn(flowId);
+            inspectDto.setFlowId(flowId);
+            inspectDto.setId(mock(ObjectId.class));
+            inspectDtoList.add(inspectDto);
+            when(inspectService.findByTaskIdList(anyList())).thenReturn(inspectDtoList);
+            doCallRealMethod().when(taskService).findDataCopyList(filter,user);
+            Page<TaskDto> actual = taskService.findDataCopyList(filter, user);
+            assertEquals(id,actual.getItems().get(0).getId());
+        }
+    }
+    @Nested
+    class GetSourceNodeTest{
+        @Test
+        @DisplayName("test getSourceNode method when dag is null")
+        void test1(){
+            when(taskDto.getDag()).thenReturn(null);
+            doCallRealMethod().when(taskService).getSourceNode(taskDto);
+            Node actual = taskService.getSourceNode(taskDto);
+            assertEquals(null,actual);
+        }
+        @Test
+        @DisplayName("test getSourceNode method normal")
+        void test2(){
+            DAG dag = mock(DAG.class);
+            LinkedList<Edge> edges = new LinkedList();
+            Edge edge = mock(Edge.class);
+            edges.add(edge);
+            when(edge.getSource()).thenReturn("111");
+            when(dag.getEdges()).thenReturn(edges);
+            List<Node> nodeList = new ArrayList<>();
+            Node node = mock(Node.class);
+            nodeList.add(node);
+            when(node.getId()).thenReturn("111");
+            when(dag.getNodes()).thenReturn(nodeList);
+            when(taskDto.getDag()).thenReturn(dag);
+            doCallRealMethod().when(taskService).getSourceNode(taskDto);
+            Node actual = taskService.getSourceNode(taskDto);
+            assertEquals(nodeList.get(0),actual);
+        }
+        @Test
+        @DisplayName("test getSourceNode method when edges is empty")
+        void test3(){
+            DAG dag = mock(DAG.class);
+            LinkedList<Edge> edges = new LinkedList();
+            when(dag.getEdges()).thenReturn(edges);
+            when(taskDto.getDag()).thenReturn(dag);
+            doCallRealMethod().when(taskService).getSourceNode(taskDto);
+            Node actual = taskService.getSourceNode(taskDto);
+            assertEquals(null,actual);
+        }
+    }
+    @Nested
+    class GetTargetNodeTest{
+        @BeforeEach
+        void beforeEach(){
+            taskDto = mock(TaskDto.class);
+            taskService = mock(TaskServiceImpl.class);
+        }
+        @Test
+        @DisplayName("test getTargetNode method normal")
+        void test1(){
+            DAG dag = mock(DAG.class);
+            LinkedList<Edge> edges = new LinkedList();
+            Edge edge = mock(Edge.class);
+            edges.add(edge);
+            when(edge.getTarget()).thenReturn("111");
+            when(dag.getEdges()).thenReturn(edges);
+            List<Node> nodeList = new ArrayList<>();
+            Node node = mock(Node.class);
+            nodeList.add(node);
+            when(node.getId()).thenReturn("111");
+            when(dag.getNodes()).thenReturn(nodeList);
+            when(taskDto.getDag()).thenReturn(dag);
+            doCallRealMethod().when(taskService).getTargetNode(taskDto);
+            Node actual = taskService.getTargetNode(taskDto);
+            assertEquals(nodeList.get(0),actual);
+        }
+        @Test
+        @DisplayName("test getTargetNode method when edges is empty")
+        void test2(){
+            DAG dag = mock(DAG.class);
+            LinkedList<Edge> edges = new LinkedList();
+            when(dag.getEdges()).thenReturn(edges);
+            when(taskDto.getDag()).thenReturn(dag);
+            doCallRealMethod().when(taskService).getTargetNode(taskDto);
+            Node actual = taskService.getTargetNode(taskDto);
+            assertEquals(null,actual);
+        }
+
+    }
+    @Nested
+    class SearchLogCollectorTest{
+        @Test
+        void test1(){
+            String key = "111";
+            doCallRealMethod().when(taskService).searchLogCollector(key);
+            LogCollectorResult actual = taskService.searchLogCollector(key);
+            assertEquals(LogCollectorResult.class,actual.getClass());
+
+        }
+    }
+    @Nested
+    class CreateShareCacheTaskTest{
+        private SaveShareCacheParam saveShareCacheParam;
+        private UserDetail user;
+        private HttpServletRequest request;
+        private HttpServletResponse response;
+        @BeforeEach
+        void beforeEach(){
+            saveShareCacheParam = mock(SaveShareCacheParam.class);
+            user = mock(UserDetail.class);
+            request = mock(HttpServletRequest.class);
+            response = mock(HttpServletResponse.class);
+        }
+        @Test
+        void testCreateShareCacheTask(){
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.confirmById(any(TaskDto.class),any(UserDetail.class),anyBoolean())).thenReturn(dto);
+            doCallRealMethod().when(taskService).createShareCacheTask(saveShareCacheParam,user,request,response);
+            TaskDto actual = taskService.createShareCacheTask(saveShareCacheParam, user, request, response);
+            assertEquals(dto,actual);
+            verify(taskService,new Times(1)).batchStart(anyList(),any(UserDetail.class),any(HttpServletRequest.class),any(HttpServletResponse.class));
+        }
+    }
+    @Nested
+    class FindShareCacheTest{
+        private Filter filter;
+        private TaskRepository repository;
+        private DataSourceServiceImpl dataSourceService;
+        private ExternalStorageService externalStorageService;
+        @BeforeEach
+        void beforeEach(){
+            repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            user = mock(UserDetail.class);
+            filter = new Filter();
+            dataSourceService = mock(DataSourceServiceImpl.class);
+            ReflectionTestUtils.setField(taskService,"dataSourceService",dataSourceService);
+            new DataPermissionHelper(mock(IDataPermissionHelper.class));
+            externalStorageService = mock(ExternalStorageService.class);
+            ReflectionTestUtils.setField(taskService,"externalStorageService",externalStorageService);
+        }
+        @Test
+        void testFindShareCacheNormal(){
+            Where where = new Where();
+            where.put("connectionName",new HashMap<>());
+            filter.setWhere(where);
+            List<TaskEntity> tasks = new ArrayList<>();
+            TaskEntity taskEntity = new TaskEntity();
+            ObjectId taskId = mock(ObjectId.class);
+            taskEntity.setId(taskId);
+            taskEntity.setName("test");
+            tasks.add(taskEntity);
+            when(repository.findAll(filter,user)).thenReturn(tasks);
+            TableNode sourceNode = mock(TableNode.class);
+            doReturn(sourceNode).when(taskService).getSourceNode(any(TaskDto.class));
+            when(dataSourceService.findOne(any(Query.class))).thenReturn(mock(DataSourceConnectionDto.class));
+            when(sourceNode.getAttrs()).thenReturn(new HashMap<>());
+            CacheNode cacheNode = mock(CacheNode.class);
+            doReturn(cacheNode).when(taskService).getTargetNode(any(TaskDto.class));
+            ObjectId id = mock(ObjectId.class);
+            when(id.toHexString()).thenReturn("111");
+            String externalStorageId = id.toHexString();
+            when(cacheNode.getExternalStorageId()).thenReturn(externalStorageId);
+            when(externalStorageService.findById(any())).thenReturn(mock(ExternalStorageDto.class));
+            doCallRealMethod().when(taskService).findShareCache(filter,user);
+            Page<ShareCacheVo> actual = taskService.findShareCache(filter, user);
+            assertEquals("test",actual.getItems().get(0).getName());
+        }
+    }
+    @Nested
+    class FindShareCacheByIdTest{
+        private String id;
+        private DataSourceServiceImpl dataSourceService;
+        @BeforeEach
+        void beforeEach(){
+            id = "111";
+            dataSourceService = mock(DataSourceServiceImpl.class);
+            ReflectionTestUtils.setField(taskService,"dataSourceService",dataSourceService);
+        }
+        @Test
+        @DisplayName("test findShareCacheById method when source node is null")
+        void test1(){
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.findById(MongoUtils.toObjectId(id))).thenReturn(dto);
+            when(taskService.getSourceNode(dto)).thenReturn(null);
+            doCallRealMethod().when(taskService).findShareCacheById(id);
+            assertThrows(BizException.class,()->taskService.findShareCacheById(id));
+        }
+        @Test
+        @DisplayName("test findShareCacheById method normal")
+        void test2(){
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.findById(MongoUtils.toObjectId(id))).thenReturn(dto);
+            TableNode dataNode = mock(TableNode.class);
+            when(taskService.getSourceNode(dto)).thenReturn(dataNode);
+            CacheNode cacheNode = mock(CacheNode.class);
+            when(taskService.getTargetNode(dto)).thenReturn(cacheNode);
+            when(dataNode.getConnectionId()).thenReturn("222");
+            DataSourceConnectionDto connectionDto = mock(DataSourceConnectionDto.class);
+            when(dataSourceService.findOne(any(Query.class))).thenReturn(connectionDto);
+            ObjectId objectId = mock(ObjectId.class);
+            when(connectionDto.getId()).thenReturn(objectId);
+            when(dataNode.getAttrs()).thenReturn(new HashMap<>());
+            when(dto.getCurrentEventTimestamp()).thenReturn(1713846744L);
+            doCallRealMethod().when(taskService).findShareCacheById(id);
+            ShareCacheDetailVo actual = taskService.findShareCacheById(id);
+            assertEquals(objectId.toString(),actual.getConnectionId());
+        }
+    }
+    @Nested
+    class ParseCacheToTaskDtoTest{
+        private SaveShareCacheParam saveShareCacheParam;
+        private TaskDto taskDto;
+        private HashMap sourceNodeMap;
+        private DataSourceServiceImpl dataSourceService;
+        @BeforeEach
+        void beforeEach(){
+            saveShareCacheParam = mock(SaveShareCacheParam.class);
+            taskDto = new TaskDto();
+            DAG dag = mock(DAG.class);
+            taskDto.setDag(dag);
+            List<Node> nodes = new ArrayList<>();
+            nodes.add(mock(Node.class));
+            when(dag.getSources()).thenReturn(nodes);
+            when(dag.getTargets()).thenReturn(nodes);
+            Map map = new HashMap();
+            ArrayList<Map> list = new ArrayList<>();
+            sourceNodeMap = mock(HashMap.class);
+            list.add(sourceNodeMap);
+            HashMap sourceNode1 = new HashMap();
+            list.add(sourceNode1);
+            map.put("nodes",list);
+            when(saveShareCacheParam.getDag()).thenReturn(map);
+            dataSourceService = mock(DataSourceServiceImpl.class);
+            ReflectionTestUtils.setField(taskService,"dataSourceService",dataSourceService);
+        }
+        @Test
+        @DisplayName("test parseCacheToTaskDto method when connectionName is null")
+        void test1(){
+            doCallRealMethod().when(taskService).parseCacheToTaskDto(saveShareCacheParam,taskDto);
+            assertThrows(BizException.class,()->taskService.parseCacheToTaskDto(saveShareCacheParam,taskDto));
+        }
+        @Test
+        @DisplayName("test parseCacheToTaskDto method normal")
+        void test2(){
+            try (MockedStatic<DAG> mb = Mockito
+                    .mockStatic(DAG.class)) {
+                DAG dag = mock(DAG.class);
+                mb.when(()->DAG.build(any(Dag.class))).thenReturn(dag);
+                when(sourceNodeMap.get("connectionId")).thenReturn("66225a960f79853715856105");
+                DataSourceConnectionDto dto = mock(DataSourceConnectionDto.class);
+                when(dto.getName()).thenReturn("test");
+                when(dataSourceService.findById(any(ObjectId.class),any(Field.class))).thenReturn(dto);
+                when(sourceNodeMap.get("attrs")).thenReturn(new HashMap<>());
+                doCallRealMethod().when(taskService).parseCacheToTaskDto(saveShareCacheParam,taskDto);
+                TaskDto actual = taskService.parseCacheToTaskDto(saveShareCacheParam, taskDto);
+                assertEquals(dag,actual.getDag());
+            }
+        }
+    }
+    @Nested
+    class InspectChartTest{
+        private TaskAutoInspectResultsService taskAutoInspectResultsService;
+        @BeforeEach
+        void beforeEach(){
+            taskAutoInspectResultsService = mock(TaskAutoInspectResultsService.class);
+            ReflectionTestUtils.setField(taskService,"taskAutoInspectResultsService",taskAutoInspectResultsService);
+        }
+        @Test
+        void testInspectChartNormal(){
+            List<TaskDto> taskDtos = new ArrayList<>();
+            TaskDto dto = mock(TaskDto.class);
+            taskDtos.add(dto);
+            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(taskDtos);
+            when(dto.getCanOpenInspect()).thenReturn(true);
+            when(dto.getStatus()).thenReturn("error");
+            ObjectId id = mock(ObjectId.class);
+            when(dto.getId()).thenReturn(id);
+            Set<String> taskSet = new HashSet<>();
+            taskSet.add(id.toHexString());
+            when(taskAutoInspectResultsService.groupByTask(user)).thenReturn(taskSet);
+            doCallRealMethod().when(taskService).inspectChart(user);
+            Map<String, Integer> actual = taskService.inspectChart(user);
+            assertEquals(1,actual.get("error"));
+            assertEquals(1,actual.get("can"));
+            assertEquals(1,actual.get("diff"));
+        }
+    }
+    @Nested
+    class GetDataCopyChartTest{
+        private List<TaskDto> migrateList;
+        @Test
+        void testGetDataCopyChartNormal(){
+            migrateList = new ArrayList<>();
+            TaskDto dto1 = mock(TaskDto.class);
+            TaskDto dto2 = mock(TaskDto.class);
+            migrateList.add(dto1);
+            migrateList.add(dto2);
+            when(dto1.getStatus()).thenReturn("paused");
+            when(dto1.getStatus()).thenReturn("scheduling");
+            doCallRealMethod().when(taskService).getDataCopyChart(migrateList);
+            Map<String, Object> actual = taskService.getDataCopyChart(migrateList);
+            assertEquals(2,actual.get("total"));
+        }
+    }
+    @Nested
+    class GetDataDevChartTest{
+        private List<TaskDto> synList;
+        @Test
+        void testGetDataDevChartNormal(){
+            synList = new ArrayList<>();
+            TaskDto dto1 = mock(TaskDto.class);
+            TaskDto dto2 = mock(TaskDto.class);
+            synList.add(dto1);
+            synList.add(dto2);
+            doCallRealMethod().when(taskService).getDataDevChart(synList);
+            Map<String, Object> actual = taskService.getDataDevChart(synList);
+            assertEquals(2,actual.size());
+        }
+    }
+    @Nested
+    class FindByIdsTest{
+        @Test
+        void testFindByIdsNormal(){
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            List<ObjectId> idList = new ArrayList<>();
+            ObjectId id = mock(ObjectId.class);
+            idList.add(id);
+            List<TaskEntity> taskEntityList = new ArrayList<>();
+            taskEntityList.add(mock(TaskEntity.class));
+            MongoTemplate template = mock(MongoTemplate.class);
+            when(repository.getMongoOperations()).thenReturn(template);
+            when(template.find(any(Query.class),any(Class.class))).thenReturn(taskEntityList);
+            List<TaskEntity> actual = taskService.findByIds(idList);
+            assertEquals(taskEntityList,actual);
+        }
+    }
+    @Nested
+    class FindTaskDetailByIdTest{
+        private String id;
+        private Field field;
+        TaskRepository repository;
+        @BeforeEach
+        void setUp(){
+            repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            user = mock(UserDetail.class);
+            new DataPermissionHelper(mock(IDataPermissionHelper.class));
+            field = mock(Field.class);
+            ObjectId objectId = new ObjectId("6613954dc8a36646da142da3");
+            id = objectId.toHexString();
+        }
+        @Test
+        @DisplayName("test findTaskDetailById method for initial sync")
+        void test1(){
+            TaskEntity task = new TaskEntity();
+            task.setId(mock(ObjectId.class));
+            task.setType("initial_sync");
+            Optional<TaskEntity> entity = Optional.ofNullable(task);
+            when(repository.findById(any(ObjectId.class),any(Field.class),any(UserDetail.class))).thenReturn(entity);
+            Date date = new Date();
+            doReturn(date).when(taskService).getMillstoneTime(any(TaskDto.class),anyString(),anyString());
+            doCallRealMethod().when(taskService).findTaskDetailById(id,field,user);
+            TaskDetailVo actual = taskService.findTaskDetailById(id, field, user);
+            assertEquals(date,actual.getInitStartTime());
+            assertEquals("initial_sync",actual.getType());
+        }
+        @Test
+        @DisplayName("test findTaskDetailById method for cdc")
+        void test2(){
+            TaskEntity task = new TaskEntity();
+            task.setId(mock(ObjectId.class));
+            task.setType("cdc");
+            Optional<TaskEntity> entity = Optional.ofNullable(task);
+            when(repository.findById(any(ObjectId.class),any(Field.class),any(UserDetail.class))).thenReturn(entity);
+            Date date = new Date();
+            doReturn(date).when(taskService).getMillstoneTime(any(TaskDto.class),anyString(),anyString());
+            doCallRealMethod().when(taskService).findTaskDetailById(id,field,user);
+            TaskDetailVo actual = taskService.findTaskDetailById(id, field, user);
+            assertEquals(date,actual.getCdcStartTime());
+            assertEquals("cdc",actual.getType());
+        }
+        @Test
+        @DisplayName("test findTaskDetailById method for initial sync")
+        void test3(){
+            TaskEntity task = new TaskEntity();
+            task.setId(mock(ObjectId.class));
+            task.setType("initial_sync+cdc");
+            Optional<TaskEntity> entity = Optional.ofNullable(task);
+            when(repository.findById(any(ObjectId.class),any(Field.class),any(UserDetail.class))).thenReturn(entity);
+            Date date = new Date();
+            doReturn(date).when(taskService).getMillstoneTime(any(TaskDto.class),anyString(),anyString());
+            doCallRealMethod().when(taskService).findTaskDetailById(id,field,user);
+            TaskDetailVo actual = taskService.findTaskDetailById(id, field, user);
+            assertEquals(date,actual.getInitStartTime());
+            assertEquals("initial_sync+cdc",actual.getType());
+        }
+    }
+    @Nested
+    class GetLastHourTest{
+        private SnapshotEdgeProgressService snapshotEdgeProgressService;
+        @Test
+        void testGetLastHourNormal(){
+            try (MockedStatic<DateUtil> mb = Mockito
+                    .mockStatic(DateUtil.class)) {
+                Date start = new Date();
+                Date end = new Date();
+                mb.when(()->DateUtil.between(start,end,DateUnit.MS)).thenThrow(new RuntimeException());
+                String taskId = "111";
+                snapshotEdgeProgressService = mock(SnapshotEdgeProgressService.class);
+                taskService = mock(TaskServiceImpl.class);
+                ReflectionTestUtils.setField(taskService,"snapshotEdgeProgressService",snapshotEdgeProgressService);
+                FullSyncVO fullSyncVO = mock(FullSyncVO.class);
+                when(snapshotEdgeProgressService.syncOverview(taskId)).thenReturn(fullSyncVO);
+                when(fullSyncVO.getStartTs()).thenReturn(start);
+                when(fullSyncVO.getEndTs()).thenReturn(end);
+                doCallRealMethod().when(taskService).getLastHour(taskId);
+                Long actual = taskService.getLastHour(taskId);
+                assertEquals(null,actual);
+            }
+        }
+    }
+    @Nested
+    class GetMillstoneTimeTest{
+        private String code;
+        private String group;
+        @Test
+        void test1(){
+            code = "111";
+            group = "222";
+            taskService = mock(TaskServiceImpl.class);
+            taskDto = mock(TaskDto.class);
+            doCallRealMethod().when(taskService).getMillstoneTime(taskDto,code,group);
+            Date actual = taskService.getMillstoneTime(taskDto, code, group);
+            assertEquals(null,actual);
+        }
+    }
+    @Nested
+    class CheckRunTest{
+        private String taskId = "111";
+        private TaskDto dto;
+        @BeforeEach
+        void setUp(){
+            dto = mock(TaskDto.class);
+            when(taskService.checkExistById(MongoUtils.toObjectId(taskId), user, "status")).thenReturn(dto);
+        }
+        @Test
+        @DisplayName("test checkRun method for edit status")
+        void test1(){
+            when(dto.getStatus()).thenReturn("edit");
+            doCallRealMethod().when(taskService).checkRun(taskId,user);
+            Boolean actual = taskService.checkRun(taskId, user);
+            assertEquals(true,actual);
+        }
+        @Test
+        @DisplayName("test checkRun method for running status")
+        void test2(){
+            when(dto.getStatus()).thenReturn("running");
+            doCallRealMethod().when(taskService).checkRun(taskId,user);
+            Boolean actual = taskService.checkRun(taskId, user);
+            assertEquals(false,actual);
+        }
+
+    }
+    @Nested
+    class FindTransformParamTest{
+        private String taskId = "111";
+        private TransformSchemaService transformSchemaService;
+        @Test
+        void testFindTransformParamNormal(){
+            taskService = spy(new TaskServiceImpl(mock(TaskRepository.class)));
+            transformSchemaService = mock(TransformSchemaService.class);
+            ReflectionTestUtils.setField(taskService,"transformSchemaService",transformSchemaService);
+            try (MockedStatic<RequestContextHolder> mb = Mockito
+                    .mockStatic(RequestContextHolder.class)) {
+                TaskDto dto = mock(TaskDto.class);
+                doReturn(dto).when(taskService).checkExistById(MongoUtils.toObjectId(taskId), user);
+                ServletRequestAttributes attributes = mock(ServletRequestAttributes.class);
+                mb.when(RequestContextHolder::currentRequestAttributes).thenReturn(attributes);
+                HttpServletRequest mockReq = mock(HttpServletRequest.class);
+                when(attributes.getRequest()).thenReturn(mockReq);
+                when(mockReq.getHeader("user-agent")).thenReturn("Java");
+                doNothing().when(taskService).deleteNotifyEnumData(anyList());
+                taskService.findTransformParam(taskId,user);
+                verify(transformSchemaService,new Times(1)).getTransformParam(dto,user);
+            }
+        }
+    }
+    @Nested
+    class FindTransformAllParam{
+        private String taskId = "111";
+        private TransformSchemaService transformSchemaService;
+        @Test
+        void testFindTransformAllParam(){
+            taskService = spy(new TaskServiceImpl(mock(TaskRepository.class)));
+            transformSchemaService = mock(TransformSchemaService.class);
+            ReflectionTestUtils.setField(taskService,"transformSchemaService",transformSchemaService);
+            try (MockedStatic<RequestContextHolder> mb = Mockito
+                    .mockStatic(RequestContextHolder.class)) {
+                doReturn(mock(TaskDto.class)).when(taskService).checkExistById(MongoUtils.toObjectId(taskId), user);
+                ServletRequestAttributes attributes = mock(ServletRequestAttributes.class);
+                mb.when(RequestContextHolder::currentRequestAttributes).thenReturn(attributes);
+                HttpServletRequest mockReq = mock(HttpServletRequest.class);
+                when(attributes.getRequest()).thenReturn(mockReq);
+                when(mockReq.getHeader("user-agent")).thenReturn("Java");
+                doNothing().when(taskService).deleteNotifyEnumData(anyList());
+                taskService.findTransformAllParam(taskId,user);
+                verify(taskService,new Times(1)).deleteNotifyEnumData(anyList());
+            }
+        }
+    }
+    @Nested
+    class SendRenewMqTest{
+        private String opType;
+        private MessageQueueServiceImpl messageQueueService;
+        @Test
+        @DisplayName("test sendRenewMq method normal")
+        void test1(){
+            messageQueueService = mock(MessageQueueServiceImpl.class);
+            ReflectionTestUtils.setField(taskService,"messageQueueService",messageQueueService);
+            taskDto = mock(TaskDto.class);
+            opType = "test";
+            when(taskDto.getId()).thenReturn(mock(ObjectId.class));
+            doCallRealMethod().when(taskService).sendRenewMq(taskDto,user,opType);
+            taskService.sendRenewMq(taskDto,user,opType);
+            verify(messageQueueService,new Times(1)).sendMessage(any(MessageQueueDto.class));
+        }
+
+    }
+    @Nested
+    class FindAgentTest{
+        private AgentGroupService agentGroupService;
+        private Worker worker;
+        @BeforeEach
+        void beforeEach(){
+            workerService = mock(WorkerService.class);
+            ReflectionTestUtils.setField(taskService,"workerService",workerService);
+            taskDto = new TaskDto();
+            user = mock(UserDetail.class);
+            agentGroupService = mock(AgentGroupService.class);
+            ReflectionTestUtils.setField(taskService,"agentGroupService",agentGroupService);
+            List<Worker> list = new ArrayList<>();
+            worker = mock(Worker.class);
+            list.add(worker);
+            when(workerService.findAvailableAgent(user)).thenReturn(list);
+        }
+        @Test
+        @DisplayName("test findAgent method when findProcessNodeListWithGroup return true")
+        void test1(){
+            List<Worker> list = new ArrayList<>();
+            list.add(mock(Worker.class));
+            when(workerService.findAvailableAgent(user)).thenReturn(list);
+            when(taskService.findProcessNodeListWithGroup(any(TaskDto.class),anyList(),any(UserDetail.class))).thenReturn(true);
+            doCallRealMethod().when(taskService).findAgent(taskDto,user);
+            boolean actual = taskService.findAgent(taskDto, user);
+            assertEquals(true,actual);
+        }
+        @Test
+        @DisplayName("test findAgent method when taskDto getAccessNodeType is manually")
+        void test2(){
+            doCallRealMethod().when(taskService).findAgent(taskDto,user);
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER");
+            List<String> idList = new ArrayList<>();
+            idList.add("111");
+            doCallRealMethod().when(taskService).findProcessNodeListWithGroup(any(TaskDto.class),anyList(),any(UserDetail.class));
+            when(agentGroupService.getProcessNodeListWithGroup(taskDto,user)).thenReturn(idList);
+            when(worker.getProcessId()).thenReturn("111");
+            boolean actual = taskService.findAgent(taskDto, user);
+            assertEquals(false,actual);
+            assertEquals("111",taskDto.getAgentId());
+        }
+        @Test
+        @DisplayName("test findAgent method when agentId is null")
+        void test3(){
+            doCallRealMethod().when(taskService).findAgent(taskDto,user);
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER");
+            List<String> idList = new ArrayList<>();
+            idList.add("111");
+            doCallRealMethod().when(taskService).findProcessNodeListWithGroup(any(TaskDto.class),anyList(),any(UserDetail.class));
+            when(agentGroupService.getProcessNodeListWithGroup(taskDto,user)).thenReturn(idList);
+            when(worker.getProcessId()).thenReturn("222");
+            boolean actual = taskService.findAgent(taskDto, user);
+            assertEquals(true,actual);
+            assertEquals(null,taskDto.getAgentId());
+        }
+        @Test
+        @DisplayName("test findAgent method when taskDto getAccessNodeType is not manually")
+        void test4(){
+            doCallRealMethod().when(taskService).findAgent(taskDto,user);
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER");
+            when(worker.getProcessId()).thenReturn("111");
+            boolean actual = taskService.findAgent(taskDto, user);
+            assertEquals(false,actual);
+            assertEquals("111",taskDto.getAgentId());
+        }
+        @Test
+        @DisplayName("test findAgent method without availableAgent")
+        void test45(){
+            List<Worker> list = new ArrayList<>();
+            when(workerService.findAvailableAgent(user)).thenReturn(list);
+            doCallRealMethod().when(taskService).findAgent(taskDto,user);
+            taskDto.setAccessNodeType("MANUALLY_SPECIFIED_BY_THE_USER");
+            boolean actual = taskService.findAgent(taskDto, user);
+            assertEquals(true,actual);
+            assertEquals(null,taskDto.getAgentId());
+        }
+    }
+    @Nested
+    class StartTest{
+        private ObjectId id;
+        private TaskDto dto;
+        private MonitoringLogsService monitoringLogsService;
+        @BeforeEach
+        void beforeEach(){
+            id = mock(ObjectId.class);
+            dto = mock(TaskDto.class);
+            monitoringLogsService = mock(MonitoringLogsService.class);
+            ReflectionTestUtils.setField(taskService,"monitoringLogsService",monitoringLogsService);
+        }
+        @Test
+        @DisplayName("test start method normal")
+        void test1(){
+            when(taskService.checkExistById(id,user)).thenReturn(dto);
+            doCallRealMethod().when(taskService).start(id,user);
+            taskService.start(id,user);
+            verify(taskService,new Times(1)).start(dto,user,"11");
+        }
+        @Test
+        @DisplayName("test start method with exception")
+        void test2(){
+            when(taskService.checkExistById(id,user)).thenReturn(dto);
+            BizException exception = mock(BizException.class);
+            doThrow(exception).when(taskService).start(dto,user,"11");
+            doCallRealMethod().when(taskService).start(id,user);
+            assertThrows(Exception.class,()->taskService.start(id,user));
+            verify(monitoringLogsService,new Times(1)).startTaskErrorLog(dto,user,exception,Level.ERROR);
+        }
+    }
+    @Nested
+    class StartWithFlagTest{
+        private String startFlag;
+        private LockControlService lockControlService;
+        private DisruptorService disruptorService;
+        private LogCollectorService logCollectorService;
+        @BeforeEach
+        void beforeEach(){
+            startFlag = "11";
+            lockControlService = mock(LockControlService.class);
+            disruptorService = mock(DisruptorService.class);
+            logCollectorService = mock(LogCollectorService.class);
+            ReflectionTestUtils.setField(taskService,"lockControlService",lockControlService);
+            ReflectionTestUtils.setField(taskService,"disruptorService",disruptorService);
+            ReflectionTestUtils.setField(taskService,"logCollectorService",logCollectorService);
+            when(taskDto.getShareCdcEnable()).thenReturn(true);
+            when(taskDto.getSyncType()).thenReturn("sync");
+            when(taskDto.getTaskRecordId()).thenReturn("111");
+            when(taskDto.getStartTime()).thenReturn(null);
+            when(taskDto.getShareCache()).thenReturn(false);
+            when(taskDto.getId()).thenReturn(mock(ObjectId.class));
+        }
+        @Test
+        @DisplayName("test start method when dag is invalid")
+        void test1(){
+            DAG dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            Map<String, List<Message>> validateMessage = new HashMap<>();
+            validateMessage.put("test",new ArrayList<>());
+            when(dag.validate()).thenReturn(validateMessage);
+            doCallRealMethod().when(taskService).start(taskDto,user,startFlag);
+            assertThrows(BizException.class,()->taskService.start(taskDto,user,startFlag));
+        }
+        @Test
+        @DisplayName("test start method when status is invalid")
+        void test2(){
+            when(taskDto.getDag()).thenReturn(mock(DAG.class));
+            doCallRealMethod().when(taskService).start(taskDto,user,startFlag);
+            assertThrows(BizException.class,()->taskService.start(taskDto,user,startFlag));
+        }
+        @Test
+        @DisplayName("test start method normal")
+        void test3(){
+            when(taskDto.getDag()).thenReturn(mock(DAG.class));
+            when(taskDto.getStatus()).thenReturn("edit");
+            when(taskDto.getSyncType()).thenReturn("migrate");
+            TaskDto transformedCheck = mock(TaskDto.class);
+            when(taskService.findByTaskId(any(ObjectId.class),anyString())).thenReturn(transformedCheck);
+            when(transformedCheck.getTransformed()).thenReturn(true);
+            doCallRealMethod().when(taskService).start(taskDto,user,startFlag);
+            taskService.start(taskDto,user,startFlag);
+            verify(taskService,new Times(1)).run(taskDto,user);
+        }
+//        @Test
+        @DisplayName("test start method when transformedCheck is false")
+        void test4(){
+            try (MockedStatic<Thread> mb = Mockito
+                    .mockStatic(Thread.class)) {
+                mb.when(()->Thread.sleep(anyLong())).thenThrow(InterruptedException.class);
+                when(taskDto.getDag()).thenReturn(mock(DAG.class));
+                when(taskDto.getStatus()).thenReturn("edit");
+                when(taskDto.getSyncType()).thenReturn("migrate");
+                TaskDto transformedCheck = mock(TaskDto.class);
+                when(taskService.findByTaskId(any(ObjectId.class),anyString())).thenReturn(transformedCheck);
+                doCallRealMethod().when(taskService).start(taskDto,user,startFlag);
+                assertThrows(BizException.class,()->taskService.start(taskDto,user,startFlag));
+            }
+        }
+        @Test
+        @DisplayName("test start method when sync type not in migrate or sync")
+        void test5(){
+            when(taskDto.getTaskRecordId()).thenReturn("");
+            when(taskDto.getDag()).thenReturn(mock(DAG.class));
+            when(taskDto.getStatus()).thenReturn("edit");
+            when(taskDto.getSyncType()).thenReturn("heartbeat");
+            doCallRealMethod().when(taskService).start(taskDto,user,startFlag);
+            taskService.start(taskDto,user,startFlag);
+            verify(taskService,new Times(1)).run(taskDto,user);
+        }
+    }
+    @Nested
+    class UpdateTaskRecordsStatusTest{
+        private TaskDto dto;
+        private String status;
+        private DisruptorService disruptorService;
+        @BeforeEach
+        void beforeEach(){
+            dto = mock(TaskDto.class);
+            user = mock(UserDetail.class);
+            disruptorService = mock(DisruptorService.class);
+            ReflectionTestUtils.setField(taskService,"disruptorService",disruptorService);
+        }
+        @Test
+        @DisplayName("test updateTaskRecordStatus method when task status is renewing")
+        void test1(){
+            status = "renewing";
+            doCallRealMethod().when(taskService).updateTaskRecordStatus(dto,status,user);
+            taskService.updateTaskRecordStatus(dto,status,user);
+            verify(dto,new Times(0)).setStatus(status);
+        }
+        @Test
+        @DisplayName("test updateTaskRecordStatus method normal")
+        void test2(){
+            status = "stopped";
+            when(dto.getTaskRecordId()).thenReturn("111");
+            when(dto.getId()).thenReturn(mock(ObjectId.class));
+            doCallRealMethod().when(taskService).updateTaskRecordStatus(dto,status,user);
+            taskService.updateTaskRecordStatus(dto,status,user);
+            verify(disruptorService,new Times(1)).sendMessage(any(DisruptorTopicEnum.class),any(SyncTaskStatusDto.class));
+        }
+    }
+    @Nested
+    class PauseTest{
+        private boolean force;
+        private boolean restart;
+        private StateMachineService stateMachineService;
+        private ObjectId id;
+        private String agentId;
+        @BeforeEach
+        void beforeEach(){
+            taskDto = mock(TaskDto.class);
+            id = mock(ObjectId.class);
+            when(taskDto.getId()).thenReturn(id);
+            agentId = "111";
+            when(taskDto.getAgentId()).thenReturn(agentId);
+            user = mock(UserDetail.class);
+            stateMachineService = mock(StateMachineService.class);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
+        }
+        @Test
+        @DisplayName("test pause method when task status is stopping")
+        void test1(){
+            restart = true;
+            when(taskDto.getStatus()).thenReturn("stopping");
+            when(taskService.findById(any(ObjectId.class),any(Field.class))).thenReturn(mock(TaskDto.class));
+            doCallRealMethod().when(taskService).pause(taskDto,user,force,restart);
+            taskService.pause(taskDto,user,force,restart);
+            verify(taskService,new Times(1)).update(any(Query.class),any(Update.class),any(UserDetail.class));
+        }
+        @Test
+        @DisplayName("test pause method when force is true")
+        void test2(){
+            force = true;
+            when(taskDto.getStatus()).thenReturn("running");
+            when(taskService.findById(any(ObjectId.class),any(Field.class))).thenReturn(mock(TaskDto.class));
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(true);
+            when(stateMachineService.executeAboutTask(taskDto, DataFlowEvent.FORCE_STOP, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).pause(taskDto,user,force,restart);
+            taskService.pause(taskDto,user,force,restart);
+            verify(stateMachineService,new Times(1)).executeAboutTask(taskDto, DataFlowEvent.FORCE_STOP, user);
+        }
+        @Test
+        @DisplayName("test pause method when force is false")
+        void test3(){
+            when(taskDto.getStatus()).thenReturn("running");
+            when(taskService.findById(any(ObjectId.class),any(Field.class))).thenReturn(mock(TaskDto.class));
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(true);
+            when(stateMachineService.executeAboutTask(taskDto, DataFlowEvent.STOP, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).pause(taskDto,user,force,restart);
+            taskService.pause(taskDto,user,force,restart);
+            verify(stateMachineService,new Times(1)).executeAboutTask(taskDto, DataFlowEvent.STOP, user);
+        }
+        @Test
+        @DisplayName("test pause method when state machine result is fail")
+        void test4(){
+            when(taskDto.getStatus()).thenReturn("running");
+            when(taskService.findById(any(ObjectId.class),any(Field.class))).thenReturn(mock(TaskDto.class));
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(true);
+            when(stateMachineService.executeAboutTask(taskDto, DataFlowEvent.STOP, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).pause(taskDto,user,force,restart);
+            taskService.pause(taskDto,user,force,restart);
+            verify(taskService,new Times(0)).sendStoppingMsg(id.toHexString(),agentId,user,force);
+        }
+        @Test
+        @DisplayName("test pause method when restart is true")
+        void test5(){
+            restart = true;
+            when(taskDto.getStatus()).thenReturn("running");
+            when(taskService.findById(any(ObjectId.class),any(Field.class))).thenReturn(mock(TaskDto.class));
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(false);
+            when(stateMachineService.executeAboutTask(taskDto, DataFlowEvent.STOP, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).pause(taskDto,user,force,restart);
+            taskService.pause(taskDto,user,force,restart);
+            verify(taskService,new Times(1)).sendStoppingMsg(id.toHexString(),agentId,user,force);
+        }
+    }
+    @Nested
+    class SendStoppingMsgTest{
+        private String taskId;
+        private String agentId;
+        private UserDetail user;
+        private boolean force;
+        private MessageQueueServiceImpl messageQueueService;
+        @Test
+        @DisplayName("test sendStoppingMsg method normal")
+        void test1(){
+            taskId = "111";
+            agentId = "222";
+            user = mock(UserDetail.class);
+            force = true;
+            messageQueueService = mock(MessageQueueServiceImpl.class);
+            ReflectionTestUtils.setField(taskService,"messageQueueService",messageQueueService);
+            doCallRealMethod().when(taskService).sendStoppingMsg(taskId,agentId,user,force);
+            taskService.sendStoppingMsg(taskId,agentId,user,force);
+            verify(messageQueueService,new Times(1)).sendMessage(any(MessageQueueDto.class));
+        }
+    }
+    @Nested
+    class RunningTest{
+        private ObjectId id;
+        private TaskDto dto;
+        private MonitoringLogsService monitoringLogsService;
+        private StateMachineService stateMachineService;
+        @BeforeEach
+        void beforeEach(){
+            id = mock(ObjectId.class);
+            dto = mock(TaskDto.class);
+            when(taskService.checkExistById(id,user, "_id", "status", "name", "taskRecordId", "startTime", "scheduleDate")).thenReturn(dto);
+            monitoringLogsService = mock(MonitoringLogsService.class);
+            stateMachineService = mock(StateMachineService.class);
+            ReflectionTestUtils.setField(taskService,"monitoringLogsService",monitoringLogsService);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
+        }
+        @Test
+        @DisplayName("test running method when task status is running")
+        void test1(){
+            when(dto.getStatus()).thenReturn("running");
+            doCallRealMethod().when(taskService).running(id,user);
+            String actual = taskService.running(id, user);
+            assertEquals(null,actual);
+        }
+        @Test
+        @DisplayName("test running method when state machine result is fail")
+        void test2(){
+            when(dto.getStatus()).thenReturn("wait_run");
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(true);
+            when(stateMachineService.executeAboutTask(dto, DataFlowEvent.RUNNING, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).running(id,user);
+            String actual = taskService.running(id, user);
+            assertEquals(null,actual);
+        }
+        @Test
+        @DisplayName("test running method normal")
+        void test3(){
+            when(dto.getStatus()).thenReturn("wait_run");
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(false);
+            when(stateMachineService.executeAboutTask(dto, DataFlowEvent.RUNNING, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).running(id,user);
+            String actual = taskService.running(id, user);
+            assertEquals(id.toHexString(),actual);
+        }
+    }
+    @Nested
+    class RunErrorTest{
+        private ObjectId id;
+        private UserDetail user;
+        private String errMsg;
+        private String errStack;
+        private TaskDto dto;
+        private StateMachineService stateMachineService;
+        @BeforeEach
+        void beforeEach(){
+            id = mock(ObjectId.class);
+            user = mock(UserDetail.class);
+            dto = mock(TaskDto.class);
+            when(taskService.checkExistById(id,user, "_id", "status", "name", "taskRecordId")).thenReturn(dto);
+            stateMachineService = mock(StateMachineService.class);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
+        }
+        @Test
+        @DisplayName("test runError method when state machine result is fail")
+        void test1(){
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(true);
+            when(stateMachineService.executeAboutTask(dto, DataFlowEvent.ERROR, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).runError(id,user,errMsg,errStack);
+            String actual = taskService.runError(id, user, errMsg, errStack);
+            assertEquals(null,actual);
+        }
+        @Test
+        @DisplayName("test runError method normal")
+        void test2(){
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(false);
+            when(stateMachineService.executeAboutTask(dto, DataFlowEvent.ERROR, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).runError(id,user,errMsg,errStack);
+            String actual = taskService.runError(id,user,errMsg,errStack);
+            assertEquals(id.toHexString(),actual);
+        }
+    }
+    @Nested
+    class CompleteTest{
+        private ObjectId id;
+        private UserDetail user;
+        private TaskDto dto;
+        private StateMachineService stateMachineService;
+        @BeforeEach
+        void beforeEach(){
+            id = mock(ObjectId.class);
+            user = mock(UserDetail.class);
+            dto = mock(TaskDto.class);
+            when(taskService.checkExistById(id,user, "_id", "status", "name", "taskRecordId")).thenReturn(dto);
+            stateMachineService = mock(StateMachineService.class);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
+        }
+        @Test
+        @DisplayName("test complete method when state machine result is fail")
+        void test1(){
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(true);
+            when(stateMachineService.executeAboutTask(dto, DataFlowEvent.COMPLETED, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).complete(id,user);
+            String actual = taskService.complete(id, user);
+            assertEquals(null,actual);
+        }
+        @Test
+        @DisplayName("test complete method normal")
+        void test2(){
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(false);
+            when(stateMachineService.executeAboutTask(dto, DataFlowEvent.COMPLETED, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).complete(id,user);
+            String actual = taskService.complete(id, user);
+            assertEquals(id.toHexString(),actual);
+        }
+    }
+    @Nested
+    class StoppedTest{
+        private ObjectId id;
+        private UserDetail user;
+        private TaskDto dto;
+        private StateMachineService stateMachineService;
+        private MonitoringLogsService monitoringLogsService;
+        private LogCollectorService logCollectorService;
+        @BeforeEach
+        void beforeEach(){
+            id = mock(ObjectId.class);
+            user = mock(UserDetail.class);
+            dto = mock(TaskDto.class);
+            when(taskService.checkExistById(id,user, "dag", "name", "status", "_id", "taskRecordId", "agentId", "stopedDate", "restartFlag")).thenReturn(dto);
+            stateMachineService = mock(StateMachineService.class);
+            monitoringLogsService = mock(MonitoringLogsService.class);
+            logCollectorService = mock(LogCollectorService.class);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
+            ReflectionTestUtils.setField(taskService,"monitoringLogsService",monitoringLogsService);
+            ReflectionTestUtils.setField(taskService,"logCollectorService",logCollectorService);
+        }
+        @Test
+        @DisplayName("test stopped method when state machine result is fail")
+        void test1(){
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(true);
+            when(stateMachineService.executeAboutTask(dto, DataFlowEvent.STOPPED, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).stopped(id,user);
+            String actual = taskService.stopped(id, user);
+            assertEquals(null,actual);
+        }
+        @Test
+        @DisplayName("test stopped method normal")
+        void test2(){
+            when(dto.getResetFlag()).thenReturn(true);
+            StateMachineResult stateMachineResult = mock(StateMachineResult.class);
+            when(stateMachineResult.isFail()).thenReturn(false);
+            when(stateMachineService.executeAboutTask(dto, DataFlowEvent.STOPPED, user)).thenReturn(stateMachineResult);
+            doCallRealMethod().when(taskService).stopped(id,user);
+            String actual = taskService.stopped(id, user);
+            verify(taskService,new Times(1)).start(id,user);
+            assertEquals(id.toHexString(),actual);
+        }
+    }
+    @Nested
+    class RunTimeInfoTest{
+        private ObjectId id;
+        private Long endTime;
+        private UserDetail user;
+        private TaskDto dto;
+        @BeforeEach
+        void beforeEach(){
+            id = mock(ObjectId.class);
+            user = mock(UserDetail.class);
+            dto = mock(TaskDto.class);
+        }
+        @Test
+        @DisplayName("test runtimeInfo method when taskDto is null")
+        void test1(){
+            when(taskService.findById(id,user)).thenReturn(null);
+            doCallRealMethod().when(taskService).runtimeInfo(id,endTime,user);
+            RunTimeInfo actual = taskService.runtimeInfo(id, endTime, user);
+            assertEquals(null,actual);
+        }
+        @Test
+        @DisplayName("test runtimeInfo method normal")
+        void test2(){
+            List<Milestone> milestones = new ArrayList<>();
+            Milestone milestone = mock(Milestone.class);
+            milestones.add(milestone);
+            when(dto.getMilestones()).thenReturn(milestones);
+            when(taskService.findById(id,user)).thenReturn(dto);
+            doCallRealMethod().when(taskService).runtimeInfo(id,endTime,user);
+            RunTimeInfo actual = taskService.runtimeInfo(id, endTime, user);
+            assertEquals(milestone,actual.getMilestones().get(0));
+        }
+    }
+    @Nested
+    class UpdateNodeTest{
+        private ObjectId objectId;
+        private String nodeId;
+        private Document param;
+        private UserDetail user;
+        @Test
+        @DisplayName("test updateNode method normal")
+        void test(){
+            objectId = mock(ObjectId.class);
+            param = mock(Document.class);
+            Map<String, Object> map = new HashMap<>();
+            map.put("test1","test2");
+            Document set = new Document(map);
+            when(param.get("$set")).thenReturn(set);
+            Set<String> str = new HashSet<>();
+            str.add("test");
+            user = mock(UserDetail.class);
+            doCallRealMethod().when(taskService).updateNode(objectId,nodeId,param,user);
+            taskService.updateNode(objectId,nodeId,param,user);
+            verify(taskService).update(any(Query.class),any(Update.class),any(UserDetail.class));
+        }
+    }
+    @Nested
+    class UpdateSyncProgressTest{
+        private ObjectId taskId;
+        private Document document;
+        @Test
+        @DisplayName("test updateSyncProgress method normal")
+        void test(){
+            taskId = mock(ObjectId.class);
+            Map<String, Object> map = new HashMap<>();
+            map.put("test1","test2");
+            document = new Document(map);
+            doCallRealMethod().when(taskService).updateSyncProgress(taskId,document);
+            taskService.updateSyncProgress(taskId,document);
+            verify(taskService).update(any(Query.class),any(Update.class));
+        }
+    }
+
+    @Test
+    void testFindRunningTasksByAgentIdWithoutId() {
+        TaskRepository repository = mock(TaskRepository.class);
+        taskService = spy(new TaskServiceImpl(repository));
+        String processId = "  ";
+        assertThrows(IllegalArgumentException.class, () -> taskService.findRunningTasksByAgentId(processId));
+    }
+
+    @Test
+    void testFindRunningTasksByAgentIdWithId() {
+        TaskRepository repository = mock(TaskRepository.class);
+        taskService = spy(new TaskServiceImpl(repository));
+        String processId = "111";
+        Query query = Query.query(Criteria.where("agentId").is(processId).and("status").is("running"));
+        when(taskService.findAll(query)).thenReturn(new ArrayList<>());
+        int actual = taskService.findRunningTasksByAgentId(processId);
+        assertEquals(0, actual);
+    }
+
+    @Nested
+    class TestCheckIsCronOrPlanTask {
+        /**
+         * 测试传入的Task任务为null
+         */
+        @Test
+        void testCheckIsCronOrPlanTaskWithNullTask() {
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            assertThrows(IllegalArgumentException.class, () -> {
+                taskService.checkIsCronOrPlanTask(null);
+            });
+        }
+
+        /**
+         * 测试传入的Task的crontabExpressionFlag属性为 null 的情况
+         */
+        @Test
+        void testCheckIsCronOrPlanTaskWithNullCronTask() {
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            TaskDto taskDto = new TaskDto();
+            boolean result = taskService.checkIsCronOrPlanTask(taskDto);
+            assertEquals(false, result);
+        }
+
+        /**
+         * 测试传入的Task的planStartDateFlag属性为true的情况
+         */
+        @Test
+        void testCheckIsCronOrPlanTaskWithTruePlanTask() {
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            TaskDto taskDto = new TaskDto();
+            taskDto.setPlanStartDateFlag(true);
+            boolean result = taskService.checkIsCronOrPlanTask(taskDto);
+            assertEquals(true, result);
+        }
+
+        /**
+         * 测试传入的Task的planStartDateFlag属性为false的情况
+         */
+        @Test
+        void testCheckIsCronOrPlanTaskWithFalsePlanTask() {
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            TaskDto taskDto = new TaskDto();
+            taskDto.setPlanStartDateFlag(false);
+            boolean result = taskService.checkIsCronOrPlanTask(taskDto);
+            assertEquals(false, result);
+        }
+
+        /**
+         * 测试传入的Task的crontabExpressionFlag属性为true的情况
+         */
+        @Test
+        void testCheckIsCronOrPlanTaskWithTrueCronTask() {
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            TaskDto taskDto = new TaskDto();
+            taskDto.setCrontabExpressionFlag(true);
+            boolean result = taskService.checkIsCronOrPlanTask(taskDto);
+            assertEquals(true, result);
+        }
+
+        /**
+         * 测试传入的Task的crontabExpressionFlag属性为false的情况
+         */
+        @Test
+        void testCheckIsCronOrPlanTaskWithFalseCronTask() {
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            TaskDto taskDto = new TaskDto();
+            taskDto.setCrontabExpressionFlag(false);
+            boolean result = taskService.checkIsCronOrPlanTask(taskDto);
+            assertEquals(false, result);
+        }
+    }
+
+    @Nested
+    class TestBatchStart {
+        final UserDetail user = new UserDetail("6393f084c162f518b18165c3", "customerId", "username", "password", "customerType",
+                "accessCode", false, false, false, false, Arrays.asList(new SimpleGrantedAuthority("role")));
+        TaskRepository repository;
+        ArrayList<TaskEntity> taskEntities = new ArrayList<>();
+        SettingsServiceImpl settingsService;
+        List<ObjectId> ids;
+        Query query;
+        TaskEntity taskEntity;
+        TaskScheduleService taskScheduleService;
+
+
+        @BeforeEach
+        void beforeEach() {
+            repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            settingsService = mock(SettingsServiceImpl.class);
+            when(settingsService.isCloud()).thenReturn(true);
+            taskService.setSettingsService(settingsService);
+            ids = Arrays.asList("6324562fc5c0a4052d821d90").stream().map(ObjectId::new).collect(Collectors.toList());
+            query = new Query(Criteria.where("_id").in(ids));
+            taskEntity = new TaskEntity();
+            taskEntity.setUserId("6393f084c162f518b18165c3");
+            taskEntity.setAgentId("632327dd287a904778c0a13c-1gd0l7dvk");
+            taskEntity.setName("test");
+            taskEntity.setId(MongoUtils.toObjectId("6324562fc5c0a4052d821d90"));
+            taskEntities.add(taskEntity);
+            taskScheduleService = mock(TaskScheduleService.class);
+            taskService.setTaskScheduleService(taskScheduleService);
+            when(repository.findAll(query)).thenReturn(taskEntities);
+        }
+
+        @Test
+        void testExceedBatchStart() {
+            try (MockedStatic<DataPermissionHelper> dataPermissionHelperMockedStatic = mockStatic(DataPermissionHelper.class)) {
+                Query query = new Query(Criteria.where("_id").is(taskEntity.getId()));
+                query.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(repository.findOne(query)).thenReturn(Optional.ofNullable(taskEntity));
+                List<TaskDto> taskDtos = CglibUtil.copyList(taskEntities, TaskDto::new);
+                CalculationEngineVo calculationEngineVo = new CalculationEngineVo();
+                calculationEngineVo.setTaskLimit(2);
+                calculationEngineVo.setRunningNum(2);
+                calculationEngineVo.setTaskLimit(2);
+                when(taskScheduleService.cloudTaskLimitNum(taskDtos.get(0), user, true)).thenReturn(calculationEngineVo);
+                MonitoringLogsService monitoringLogsService = mock(MonitoringLogsService.class);
+                taskService.setMonitoringLogsService(monitoringLogsService);
+                List<MutiResponseMessage> mutiResponseMessages = taskService.batchStart(ids, user, null, null);
+                assertEquals("Task.ScheduleLimit", mutiResponseMessages.get(0).getCode());
+            }
+        }
+
+        @Test
+        void testBatchStart() {
+            try (MockedStatic<DataPermissionHelper> dataPermissionHelperMockedStatic = mockStatic(DataPermissionHelper.class)) {
+                taskEntity.setCrontabExpressionFlag(true);
+                Query query = new Query(Criteria.where("_id").is(taskEntity.getId()));
+                query.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(repository.findOne(query)).thenReturn(Optional.ofNullable(taskEntity));
+                List<TaskDto> taskDtos = CglibUtil.copyList(taskEntities, TaskDto::new);
+                CalculationEngineVo calculationEngineVo = new CalculationEngineVo();
+                calculationEngineVo.setTaskLimit(2);
+                calculationEngineVo.setRunningNum(2);
+                calculationEngineVo.setTaskLimit(2);
+                calculationEngineVo.setTotalLimit(2);
+                when(taskScheduleService.cloudTaskLimitNum(taskDtos.get(0), user, true)).thenReturn(calculationEngineVo);
+                MonitoringLogsService monitoringLogsService = mock(MonitoringLogsService.class);
+                taskService.setMonitoringLogsService(monitoringLogsService);
+                taskService.batchStart(ids, user, null, null);
+                verify(taskService, times(1)).start(taskDtos.get(0), user, "11");
+            }
+        }
+    }
+
+    @Nested
+    class TestRun {
+        final UserDetail user = new UserDetail("6393f084c162f518b18165c3", "customerId", "username", "password", "customerType",
+                "accessCode", false, false, false, false, Arrays.asList(new SimpleGrantedAuthority("role")));
+        TaskRepository repository;
+        SettingsServiceImpl settingsService;
+        TaskEntity taskEntity;
+        TaskScheduleService taskScheduleService;
+
+        @BeforeEach
+        void beforeEach() {
+            repository=mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            taskEntity=new TaskEntity();
+            taskEntity.setUserId("6393f084c162f518b18165c3");
+            taskEntity.setAgentId("632327dd287a904778c0a13c-1gd0l7dvk");
+            taskEntity.setName("test");
+            taskEntity.setId(MongoUtils.toObjectId("6324562fc5c0a4052d821d90"));
+            settingsService=mock(SettingsServiceImpl.class);
+            when(settingsService.isCloud()).thenReturn(true);
+            taskService.setSettingsService(settingsService);
+            taskScheduleService=mock(TaskScheduleService.class);
+            taskService.setTaskScheduleService(taskScheduleService);
+        }
+
+        @Test
+        void testNoExceedRun() {
+            try (MockedStatic<DataPermissionHelper> dataPermissionHelperMockedStatic = mockStatic(DataPermissionHelper.class)) {
+                taskEntity.setCrontabExpressionFlag(true);
+                TaskDto taskDto = new TaskDto();
+                copyProperties(taskEntity, taskDto, TaskDto.class);
+                Query query = new Query(Criteria.where("_id").is(taskDto.getId()));
+                query.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(repository.findOne(query)).thenReturn(Optional.ofNullable(taskEntity));
+                CalculationEngineVo calculationEngineVo = new CalculationEngineVo();
+                calculationEngineVo.setTaskLimit(2);
+                calculationEngineVo.setRunningNum(2);
+                calculationEngineVo.setTaskLimit(2);
+                calculationEngineVo.setTotalLimit(2);
+                when(taskScheduleService.cloudTaskLimitNum(taskDto, user, true)).thenReturn(calculationEngineVo);
+                StateMachineService stateMachineService = mock(StateMachineService.class);
+                taskService.setStateMachineService(stateMachineService);
+                when(stateMachineService.executeAboutTask(taskDto, DataFlowEvent.START, user)).thenReturn(StateMachineResult.ok());
+                taskService.run(taskDto, user);
+                verify(stateMachineService, times(1)).executeAboutTask(taskDto, DataFlowEvent.START, user);
+            }
+        }
+
+        @Test
+        void testExceedRun() {
+            try (MockedStatic<DataPermissionHelper> dataPermissionHelperMockedStatic = mockStatic(DataPermissionHelper.class)) {
+                TaskDto taskDto = new TaskDto();
+                copyProperties(taskEntity, taskDto, TaskDto.class);
+                Query query = new Query(Criteria.where("_id").is(taskDto.getId()));
+                query.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(repository.findOne(query)).thenReturn(Optional.ofNullable(taskEntity));
+                TaskScheduleService taskScheduleService = mock(TaskScheduleService.class);
+                taskService.setTaskScheduleService(taskScheduleService);
+                CalculationEngineVo calculationEngineVo = new CalculationEngineVo();
+                calculationEngineVo.setTaskLimit(2);
+                calculationEngineVo.setRunningNum(2);
+                calculationEngineVo.setTaskLimit(2);
+                calculationEngineVo.setTotalLimit(2);
+                when(taskScheduleService.cloudTaskLimitNum(taskDto, user, true)).thenReturn(calculationEngineVo);
+                StateMachineService stateMachineService = mock(StateMachineService.class);
+                taskService.setStateMachineService(stateMachineService);
+                assertThrows(BizException.class, () -> taskService.run(taskDto, user));
+            }
+        }
+    }
+    @Nested
+    class TestSubCronOrPlanNum{
+        TaskRepository taskRepository;
+        TaskEntity taskEntity;
+        TaskDto taskDto;
+        @BeforeEach
+        void beforeEach(){
+            taskEntity=new TaskEntity();
+            taskEntity.setId(MongoUtils.toObjectId("6324562fc5c0a4052d821d90"));
+
+            taskDto=new TaskDto();
+            BeanUtils.copyProperties(taskEntity,taskDto);
+            taskRepository = mock(TaskRepository.class);
+            taskService=spy(new TaskServiceImpl(taskRepository));
+            Query query = new Query(Criteria.where("_id").is(taskDto.getId()));
+            query.fields().include("planStartDateFlag", "crontabExpressionFlag");
+            when(taskRepository.findOne(query)).thenReturn(Optional.ofNullable(taskEntity));
+        }
+        @DisplayName("test cron task sub 1")
+        @Test
+        void testSubCronOrPlanNum(){
+            taskEntity.setCrontabExpressionFlag(true);
+            try (MockedStatic<DataPermissionHelper> dataPermissionHelperMockedStatic = mockStatic(DataPermissionHelper.class)) {
+                int result = taskService.subCronOrPlanNum(taskDto, 3);
+                assertEquals(2,result);
+            }
+        }
+        @DisplayName("test not cron task don't sub 1")
+        @Test
+        void testNoCronOrPlanTask(){
+            taskEntity.setCrontabExpressionFlag(null);
+            try (MockedStatic<DataPermissionHelper> dataPermissionHelperMockedStatic = mockStatic(DataPermissionHelper.class)) {
+                int result = taskService.subCronOrPlanNum(taskDto, 3);
+                assertEquals(3,result);
+            }
+        }
+    }
+
+    @Nested
+    class TestCheckCloudTaskLimit {
+        TaskRepository taskRepository = mock(TaskRepository.class);
+
+        SettingsServiceImpl settingsService = mock(SettingsServiceImpl.class);
+
+        TaskScheduleService taskScheduleService = mock(TaskScheduleService.class);
+        WorkerService workerService = mock(WorkerService.class);
+
+        final UserDetail user = new UserDetail("6393f084c162f518b18165c3", "customerId", "username", "password", "customerType",
+                "accessCode", false, false, false, false, Arrays.asList(new SimpleGrantedAuthority("role")));
+        @BeforeEach
+        void beforeEach() {
+            new DataPermissionHelper(mock(IDataPermissionHelper.class)); //when repository.find call methods in DataPermissionHelper class this line is need
+            taskService = new TaskServiceImpl(taskRepository);
+            taskService.setSettingsService(settingsService);
+            taskService.setTaskScheduleService(taskScheduleService);
+            ReflectionTestUtils.setField(taskService,"workerService",workerService);
+        }
+        @Test
+        void test_isDass(){
+            when(settingsService.isCloud()).thenReturn(false);
+            ObjectId taskId = MongoUtils.toObjectId("632454d5287a904778c40f8d");
+            boolean result = taskService.checkCloudTaskLimit(taskId,user,true);
+            assertTrue(result);
+        }
+
+        @Test
+        void test_isCloudLimit(){
+            ObjectId taskId = MongoUtils.toObjectId("632454d5287a904778c40f8d");
+            Query query = new Query(Criteria.where("_id").is(taskId));
+            query.fields().include("id","agentId","agentTags");
+            TaskEntity mockTask = new TaskEntity();
+            mockTask.setCrontabExpressionFlag(false);
+            mockTask.setId(MongoUtils.toObjectId("632454d5287a904778c40f8d"));
+            try (MockedStatic<DataPermissionService> serviceMockedStatic = Mockito.mockStatic(DataPermissionService.class)) {
+                serviceMockedStatic.when(DataPermissionService::isCloud).thenReturn(true);
+                when(settingsService.isCloud()).thenReturn(true);
+                when(taskRepository.findOne(query)).thenReturn(Optional.of(mockTask));
+                TaskDto mockTaskDto = new TaskDto();
+                BeanUtils.copyProperties(mockTask,mockTaskDto);
+                CalculationEngineVo mockEngineVo = new CalculationEngineVo();
+                mockEngineVo.setTaskLimit(5);
+                mockEngineVo.setRunningNum(5);
+                when(workerService.calculationEngine(mockTaskDto,user,null)).thenReturn(mockEngineVo);
+                Query mockQuery = new Query(Criteria.where("_id").is(taskId));
+                mockQuery.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(taskRepository.findOne(mockQuery)).thenReturn(Optional.of(mockTask));
+                boolean result = taskService.checkCloudTaskLimit(taskId,user,true);
+                assertFalse(result);
+            }
+        }
+
+        @Test
+        void test_isCloudLimitNotReached(){
+            ObjectId taskId = MongoUtils.toObjectId("632454d5287a904778c40f8d");
+            Query query = new Query(Criteria.where("_id").is(taskId));
+            query.fields().include("id","agentId","agentTags");
+            TaskEntity mockTask = new TaskEntity();
+            mockTask.setCrontabExpressionFlag(false);
+            mockTask.setId(MongoUtils.toObjectId("632454d5287a904778c40f8d"));
+            try (MockedStatic<DataPermissionService> serviceMockedStatic = Mockito.mockStatic(DataPermissionService.class)) {
+                serviceMockedStatic.when(DataPermissionService::isCloud).thenReturn(true);
+                when(settingsService.isCloud()).thenReturn(true);
+                when(taskRepository.findOne(query)).thenReturn(Optional.of(mockTask));
+                TaskDto mockTaskDto = new TaskDto();
+                BeanUtils.copyProperties(mockTask,mockTaskDto);
+                CalculationEngineVo mockEngineVo = new CalculationEngineVo();
+                mockEngineVo.setTaskLimit(5);
+                mockEngineVo.setRunningNum(4);
+                when(workerService.calculationEngine(mockTaskDto,user,null)).thenReturn(mockEngineVo);
+                Query mockQuery = new Query(Criteria.where("_id").is(taskId));
+                mockQuery.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(taskRepository.findOne(mockQuery)).thenReturn(Optional.of(mockTask));
+                boolean result = taskService.checkCloudTaskLimit(taskId,user,true);
+                assertTrue(result);
+            }
+        }
+
+        @Test
+        void test_isCloudLimitScheduling(){
+            ObjectId taskId = MongoUtils.toObjectId("632454d5287a904778c40f8d");
+            Query query = new Query(Criteria.where("_id").is(taskId));
+            query.fields().include("id","agentId","agentTags");
+            TaskEntity mockTask = new TaskEntity();
+            mockTask.setCrontabExpressionFlag(true);
+            mockTask.setId(MongoUtils.toObjectId("632454d5287a904778c40f8d"));
+            try (MockedStatic<DataPermissionService> serviceMockedStatic = Mockito.mockStatic(DataPermissionService.class)) {
+                serviceMockedStatic.when(DataPermissionService::isCloud).thenReturn(true);
+                when(settingsService.isCloud()).thenReturn(true);
+                when(taskRepository.findOne(query)).thenReturn(Optional.of(mockTask));
+                TaskDto mockTaskDto = new TaskDto();
+                BeanUtils.copyProperties(mockTask,mockTaskDto);
+                CalculationEngineVo mockEngineVo = new CalculationEngineVo();
+                mockEngineVo.setTaskLimit(5);
+                mockEngineVo.setRunningNum(5);
+                when(workerService.calculationEngine(mockTaskDto,user,null)).thenReturn(mockEngineVo);
+                Query mockQuery = new Query(Criteria.where("_id").is(taskId));
+                mockQuery.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(taskRepository.findOne(mockQuery)).thenReturn(Optional.of(mockTask));
+                boolean result = taskService.checkCloudTaskLimit(taskId,user,true);
+                assertTrue(result);
+            }
+        }
+    }
+
+    @Nested
+    class TestCopy{
+        TaskRepository taskRepository = mock(TaskRepository.class);
+
+        SettingsServiceImpl settingsService = mock(SettingsServiceImpl.class);
+
+        TaskScheduleService taskScheduleService = mock(TaskScheduleService.class);
+
+        UserLogService serLogService = mock(UserLogService.class);
+
+        WorkerService workerService = mock(WorkerService.class);
+
+        final UserDetail user = new UserDetail("6393f084c162f518b18165c3", "customerId", "username", "password", "customerType",
+                "accessCode", false, false, false, false, Arrays.asList(new SimpleGrantedAuthority("role")));
+        @BeforeEach
+        void beforeEach() {
+            new DataPermissionHelper(mock(IDataPermissionHelper.class)); //when repository.find call methods in DataPermissionHelper class this line is need
+            taskService = new TaskServiceImpl(taskRepository);
+            taskService.setSettingsService(settingsService);
+            taskService.setTaskScheduleService(taskScheduleService);
+            taskService.setUserLogService(serLogService);
+            ReflectionTestUtils.setField(taskService,"workerService",workerService);
+        }
+
+        @Test
+        void test_copySchedulingTask(){
+            ObjectId taskId = MongoUtils.toObjectId("632454d5287a904778c40f8d");
+            TaskEntity mockTask = new TaskEntity();
+            mockTask.setCrontabExpressionFlag(true);
+            mockTask.setCrontabExpression("test");
+            mockTask.setId(MongoUtils.toObjectId("632454d5287a904778c40f8d"));
+            try (MockedStatic<DataPermissionService> serviceMockedStatic = Mockito.mockStatic(DataPermissionService.class);
+                 MockedStatic<SpringContextHelper> helperMockedStatic = Mockito.mockStatic(SpringContextHelper.class)) {
+                serviceMockedStatic.when(DataPermissionService::isCloud).thenReturn(true);
+                TaskDto mockTaskDto = new TaskDto();
+                BeanUtils.copyProperties(mockTask,mockTaskDto);
+                when(taskRepository.findById(taskId,user)).thenReturn(Optional.of(mockTask));
+                when(settingsService.isCloud()).thenReturn(true);
+                Query query = new Query(Criteria.where("_id").is(taskId));
+                query.fields().include("id","agentId","agentTags");
+                when(taskRepository.findOne(query)).thenReturn(Optional.of(mockTask));
+                CalculationEngineVo mockEngineVo = new CalculationEngineVo();
+                mockEngineVo.setTaskLimit(5);
+                mockEngineVo.setRunningNum(4);
+                when(workerService.calculationEngine(mockTaskDto,user,null)).thenReturn(mockEngineVo);
+                Query mockQuery = new Query(Criteria.where("_id").is(taskId));
+                mockQuery.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(taskRepository.findOne(mockQuery)).thenReturn(Optional.of(mockTask));
+                TaskServiceImpl mockTaskService = mock(TaskServiceImpl.class);
+                helperMockedStatic.when(()->SpringContextHelper.getBean(TaskServiceImpl.class)).thenReturn(mockTaskService);
+                when(mockTaskService.confirmById(any(TaskDto.class),any(UserDetail.class),any(Boolean.class))).thenAnswer(invocationOnMock -> {
+                    return invocationOnMock.<TaskDto>getArgument(0);
+                });
+                TaskDto result = taskService.copy(taskId,user);
+                assertTrue(result.getCrontabExpressionFlag());
+                assertEquals("test",result.getCrontabExpression());
+            }
+        }
+
+        @Test
+        void test_copySchedulingTaskLimit(){
+            ObjectId taskId = MongoUtils.toObjectId("632454d5287a904778c40f8d");
+            TaskEntity mockTask = new TaskEntity();
+            mockTask.setCrontabExpressionFlag(true);
+            mockTask.setId(MongoUtils.toObjectId("632454d5287a904778c40f8d"));
+            try (MockedStatic<DataPermissionService> serviceMockedStatic = Mockito.mockStatic(DataPermissionService.class);
+                 MockedStatic<SpringContextHelper> helperMockedStatic = Mockito.mockStatic(SpringContextHelper.class)) {
+                serviceMockedStatic.when(DataPermissionService::isCloud).thenReturn(true);
+                TaskDto mockTaskDto = new TaskDto();
+                BeanUtils.copyProperties(mockTask,mockTaskDto);
+                when(taskRepository.findById(taskId,user)).thenReturn(Optional.of(mockTask));
+                when(settingsService.isCloud()).thenReturn(true);
+                Query query = new Query(Criteria.where("_id").is(taskId));
+                query.fields().include("id","agentId","agentTags");
+                when(taskRepository.findOne(query)).thenReturn(Optional.of(mockTask));
+                CalculationEngineVo mockEngineVo = new CalculationEngineVo();
+                mockEngineVo.setTaskLimit(5);
+                mockEngineVo.setRunningNum(5);
+                when(workerService.calculationEngine(mockTaskDto,user,null)).thenReturn(mockEngineVo);
+                Query mockQuery = new Query(Criteria.where("_id").is(taskId));
+                mockQuery.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(taskRepository.findOne(mockQuery)).thenReturn(Optional.of(mockTask));
+                TaskServiceImpl mockTaskService = mock(TaskServiceImpl.class);
+                helperMockedStatic.when(()->SpringContextHelper.getBean(TaskServiceImpl.class)).thenReturn(mockTaskService);
+                when(mockTaskService.confirmById(any(TaskDto.class),any(UserDetail.class),any(Boolean.class))).thenAnswer(invocationOnMock -> {
+                    return invocationOnMock.<TaskDto>getArgument(0);
+                });
+                TaskDto result = taskService.copy(taskId,user);
+                assertFalse(result.getCrontabExpressionFlag());
+                assertNull(result.getCrontabExpression());
+            }
+        }
+
+        @Test
+        void test_copyNormalTaskLimit(){
+            ObjectId taskId = MongoUtils.toObjectId("632454d5287a904778c40f8d");
+            TaskEntity mockTask = new TaskEntity();
+            mockTask.setId(MongoUtils.toObjectId("632454d5287a904778c40f8d"));
+            try (MockedStatic<DataPermissionService> serviceMockedStatic = Mockito.mockStatic(DataPermissionService.class);
+                 MockedStatic<SpringContextHelper> helperMockedStatic = Mockito.mockStatic(SpringContextHelper.class)) {
+                serviceMockedStatic.when(DataPermissionService::isCloud).thenReturn(true);
+                TaskDto mockTaskDto = new TaskDto();
+                BeanUtils.copyProperties(mockTask,mockTaskDto);
+                when(taskRepository.findById(taskId,user)).thenReturn(Optional.of(mockTask));
+                when(settingsService.isCloud()).thenReturn(true);
+                Query query = new Query(Criteria.where("_id").is(taskId));
+                query.fields().include("id","agentId","agentTags");
+                when(taskRepository.findOne(query)).thenReturn(Optional.of(mockTask));
+                CalculationEngineVo mockEngineVo = new CalculationEngineVo();
+                mockEngineVo.setTaskLimit(5);
+                mockEngineVo.setRunningNum(4);
+                when(workerService.calculationEngine(mockTaskDto,user,null)).thenReturn(mockEngineVo);
+                Query mockQuery = new Query(Criteria.where("_id").is(taskId));
+                mockQuery.fields().include("planStartDateFlag", "crontabExpressionFlag");
+                when(taskRepository.findOne(mockQuery)).thenReturn(Optional.of(mockTask));
+                TaskServiceImpl mockTaskService = mock(TaskServiceImpl.class);
+                helperMockedStatic.when(()->SpringContextHelper.getBean(TaskServiceImpl.class)).thenReturn(mockTaskService);
+                when(mockTaskService.confirmById(any(TaskDto.class),any(UserDetail.class),any(Boolean.class))).thenAnswer(invocationOnMock -> {
+                    return invocationOnMock.<TaskDto>getArgument(0);
+                });
+                TaskDto result = taskService.copy(taskId,user);
+                assertNull(result.getCrontabExpressionFlag());
+                assertNull(result.getCrontabExpression());
+            }
+        }
+    }
+    @Nested
+    class TestRunningTaskNum{
+        TaskRepository taskRepository = mock(TaskRepository.class);
+        final UserDetail user = new UserDetail("6393f084c162f518b18165c3", "customerId", "username", "password", "customerType",
+                "accessCode", false, false, false, false, Arrays.asList(new SimpleGrantedAuthority("role")));
+        @BeforeEach
+        void beforeEach() {
+            taskService = new TaskServiceImpl(taskRepository);
+        }
+        @Test
+        void testRunningTaskNum(){
+            long except = 5L;
+            when(taskRepository.count(Query.query(Criteria.where("is_deleted").ne(true)
+                    .and("syncType").in(TaskDto.SYNC_TYPE_SYNC, TaskDto.SYNC_TYPE_MIGRATE)
+                    .and("status").nin(TaskDto.STATUS_DELETE_FAILED,TaskDto.STATUS_DELETING)
+                    .orOperator(Criteria.where("status").in(TaskDto.STATUS_RUNNING, TaskDto.STATUS_SCHEDULING, TaskDto.STATUS_WAIT_RUN),
+                            Criteria.where("planStartDateFlag").is(true),
+                            Criteria.where("crontabExpressionFlag").is(true)
+                    )),user)).thenReturn(except);
+            long result = taskService.runningTaskNum(user);
+            assertEquals(except,result);
+        }
+    }
+    @Nested
+    class ChartTest{
+        TaskRepository taskRepository = mock(TaskRepository.class);
+        //        @Test
+        void testChartNormal(){
+            new DataPermissionHelper(mock(DataPermissionHelperImpl.class)); //when repository.find call methods in DataPermissionHelper class this line is need
+            try (MockedStatic<DataPermissionService> mb = Mockito
+                    .mockStatic(DataPermissionService.class)) {
+                mb.when(DataPermissionService::isCloud).thenReturn(true);
+                taskService = spy(new TaskServiceImpl(taskRepository));
+                UserDetail user = mock(UserDetail.class);
+                DataPermissionMenuEnums permission = mock(DataPermissionMenuEnums.class);
+                List<TaskDto> taskDtoList = new ArrayList<>();
+                TaskDto taskDto1 = new TaskDto();
+                taskDto1.setStatus("stop");
+                taskDto1.setSyncType("migrate");
+                TaskDto taskDto2 = new TaskDto();
+                taskDto2.setStatus("wait_start");
+                taskDto2.setSyncType("migrate");
+                TaskDto taskDto3 = new TaskDto();
+                taskDto3.setStatus("edit");
+                taskDto3.setSyncType("migrate");
+                TaskDto taskDto4 = new TaskDto();
+                taskDto4.setStatus("stop");
+                taskDto4.setSyncType("sync");
+                TaskDto taskDto5 = new TaskDto();
+                taskDto5.setStatus("stop");
+                taskDto5.setSyncType("sync");
+                taskDtoList.add(taskDto1);
+                taskDtoList.add(taskDto2);
+                taskDtoList.add(taskDto3);
+                taskDtoList.add(taskDto4);
+                taskDtoList.add(taskDto5);
+                doReturn(taskDtoList).when(taskService).findAllDto(any(),any());
+                when(permission.MigrateTack.checkAndSetFilter(user, DataPermissionActionEnums.View, () -> taskService.findAllDto(any(),any()))).thenReturn(taskDtoList);
+                doReturn(new HashMap()).when(taskService).inspectChart(user);
+                Chart6Vo chart6Vo = mock(Chart6Vo.class);
+                doReturn(chart6Vo).when(taskService).chart6(user);
+                Map<String, Object> actual = taskService.chart(user);
+                Map chart1 = (Map) actual.get("chart1");
+                assertEquals(3,chart1.get("total"));
+                Map chart3 = (Map) actual.get("chart3");
+                assertEquals(2,chart3.get("total"));
+                Map chart5 = (Map) actual.get("chart5");
+                assertEquals(0,chart5.size());
+                assertEquals(chart6Vo,actual.get("chart6"));
+            }
+
+        }
+    }
+    @Nested
+    class RunningTaskNumWithProcessIdTest{
+        @Test
+        void testRunningTaskNumWithProcessId(){
+            TaskRepository taskRepository = mock(TaskRepository.class);
+            taskService = new TaskServiceImpl(taskRepository);
+            long except = 5L;
+            UserDetail userDetail = mock(UserDetail.class);
+            when(taskRepository.count(Query.query(Criteria.where("agentId").is("111")
+                    .and("is_deleted").ne(true).and("syncType").in(TaskDto.SYNC_TYPE_SYNC, TaskDto.SYNC_TYPE_MIGRATE)
+                    .and("status").nin(TaskDto.STATUS_DELETE_FAILED,TaskDto.STATUS_DELETING)
+                    .orOperator(Criteria.where("status").in(TaskDto.STATUS_RUNNING, TaskDto.STATUS_SCHEDULING, TaskDto.STATUS_WAIT_RUN),
+                            Criteria.where("planStartDateFlag").is(true),
+                            Criteria.where("crontabExpressionFlag").is(true)
+                    )), userDetail)).thenReturn(except);
+            long result = taskService.runningTaskNum("111", userDetail);
+            assertEquals(except,result);
+        }
+    }
+    @Nested
+    class RenameTest{
+        private String taskId;
+        private String newName;
+        @Test
+        @DisplayName("test rename method when newName equals taskDto name")
+        void test1(){
+            taskId = "662877df9179877be8b37074";
+            newName = "name";
+            when(taskDto.getName()).thenReturn("name");
+            when(taskService.checkExistById(any(ObjectId.class),any(UserDetail.class),anyString())).thenReturn(taskDto);
+            doCallRealMethod().when(taskService).rename(taskId,newName,user);
+            taskService.rename(taskId,newName,user);
+            verify(taskService,never()).checkTaskName(newName,user,MongoUtils.toObjectId(taskId));
+        }
+        @Test
+        @DisplayName("test rename method normal")
+        void test2(){
+            taskId = "662877df9179877be8b37074";
+            newName = "name";
+            when(taskDto.getName()).thenReturn("task");
+            when(taskService.checkExistById(any(ObjectId.class),any(UserDetail.class),anyString())).thenReturn(taskDto);
+            doCallRealMethod().when(taskService).rename(taskId,newName,user);
+            taskService.rename(taskId,newName,user);
+            verify(taskService).checkTaskName(newName,user,MongoUtils.toObjectId(taskId));
+            verify(taskService).updateById(any(ObjectId.class),any(Update.class),any(UserDetail.class));
+        }
+    }
+    @Nested
+    class StatsTest{
+        @Test
+        void testStatsNormal(){
+            Map<String, Long> taskTypeStats = new HashMap<>();
+            when(taskService.typeTaskStats(user)).thenReturn(taskTypeStats);
+            doCallRealMethod().when(taskService).stats(user);
+            TaskStatsDto actual = taskService.stats(user);
+            assertEquals(taskTypeStats,actual.getTaskTypeStats());
+
+        }
+    }
+    @Nested
+    class TypeTaskStatsTest{
+        @Test
+        void testTypeTaskStats(){
+            TaskRepository repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            List<TaskServiceImpl.Char1Group> mappedResults = new ArrayList();
+            TaskServiceImpl.Char1Group char1Group = new TaskServiceImpl.Char1Group();
+            char1Group.set_id("111");
+            mappedResults.add(char1Group);
+            Document rawResults = new Document();
+            AggregationResults<TaskServiceImpl.Char1Group> result = new AggregationResults<>(mappedResults,rawResults);
+            when(repository.aggregate(any(org.springframework.data.mongodb.core.aggregation.Aggregation.class),any(Class.class))).thenReturn(result);
+            Map<String, Long> actual = taskService.typeTaskStats(user);
+            assertEquals(0,actual.get("total"));
+        }
+    }
+    @Nested
+    class MergerStatisticsTest{
+        private List<LocalDate> localDates;
+        private DataFlowInsightStatisticsDto oldStatistics;
+        private com.tapdata.tm.dataflowinsight.dto.DataFlowInsightStatisticsDto newStatistics;
+        private String time;
+        @BeforeEach
+        void beforeEach(){
+            localDates = new ArrayList<>();
+            LocalDate localDate = LocalDate.now();
+            localDates.add(localDate);
+            final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMdd");
+            time = localDate.format(format);
+        }
+        @Test
+        @DisplayName("test mergerStatistics method when newMap get time not null")
+        void test1(){
+            oldStatistics = new DataFlowInsightStatisticsDto();
+            List<DataFlowInsightStatisticsDto.DataStatisticInfo> inputData = new ArrayList<>();
+            inputData.add(new DataFlowInsightStatisticsDto.DataStatisticInfo(time,new BigInteger("1")));
+            newStatistics = new DataFlowInsightStatisticsDto();
+            newStatistics.setInputDataStatistics(inputData);
+            doCallRealMethod().when(taskService).mergerStatistics(localDates,oldStatistics,newStatistics);
+            DataFlowInsightStatisticsDto actual = taskService.mergerStatistics(localDates, oldStatistics, newStatistics);
+            assertEquals(new BigInteger("1"),actual.getTotalInputDataCount());
+        }
+        @Test
+        @DisplayName("test mergerStatistics method when newMAp get time not null")
+        void test2(){
+            oldStatistics = new DataFlowInsightStatisticsDto();
+            List<DataFlowInsightStatisticsDto.DataStatisticInfo> inputData = new ArrayList<>();
+            inputData.add(new DataFlowInsightStatisticsDto.DataStatisticInfo(time,new BigInteger("1")));
+            oldStatistics.setInputDataStatistics(inputData);
+            newStatistics = new DataFlowInsightStatisticsDto();
+            doCallRealMethod().when(taskService).mergerStatistics(localDates,oldStatistics,newStatistics);
+            DataFlowInsightStatisticsDto actual = taskService.mergerStatistics(localDates, oldStatistics, newStatistics);
+            assertEquals(new BigInteger("1"),actual.getTotalInputDataCount());
+        }
+    }
+    @Nested
+    class GetNewLocalDateTest{
+        private List<LocalDate> localDates;
+        private DataFlowInsightStatisticsDto oldStatistics;
+        private String time;
+        private LocalDate localDate;
+        @BeforeEach
+        void beforeEach(){
+            localDates = new ArrayList<>();
+            localDate = LocalDate.now();
+            localDates.add(localDate);
+            final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMdd");
+            time = localDate.format(format);
+            oldStatistics = new DataFlowInsightStatisticsDto();
+        }
+        @Test
+        @DisplayName("test getNewLocalDate method when inputDataStatistics is empty")
+        void test1(){
+            List<DataFlowInsightStatisticsDto.DataStatisticInfo> inputData = new ArrayList<>();
+            oldStatistics.setInputDataStatistics(inputData);
+            doCallRealMethod().when(taskService).getNewLocalDate(localDates,oldStatistics);
+            List<LocalDate> actual = taskService.getNewLocalDate(localDates, oldStatistics);
+            assertEquals(localDate,actual.get(0));
+        }
+        @Test
+        @DisplayName("test getNewLocalDate method when oldLocalDate contains localDate")
+        void test2(){
+            List<DataFlowInsightStatisticsDto.DataStatisticInfo> inputData = new ArrayList<>();
+            inputData.add(new DataFlowInsightStatisticsDto.DataStatisticInfo(time,new BigInteger("1")));
+            inputData.add(new DataFlowInsightStatisticsDto.DataStatisticInfo("20240101",new BigInteger("1")));
+            oldStatistics.setInputDataStatistics(inputData);
+            doCallRealMethod().when(taskService).getNewLocalDate(localDates,oldStatistics);
+            List<LocalDate> actual = taskService.getNewLocalDate(localDates, oldStatistics);
+            assertEquals(0,actual.size());
+        }
+        @Test
+        @DisplayName("test getNewLocalDate method when oldLocalDate not contains localDate")
+        void test3(){
+            List<DataFlowInsightStatisticsDto.DataStatisticInfo> inputData = new ArrayList<>();
+            inputData.add(new DataFlowInsightStatisticsDto.DataStatisticInfo("20240101",new BigInteger("1")));
+            oldStatistics.setInputDataStatistics(inputData);
+            doCallRealMethod().when(taskService).getNewLocalDate(localDates,oldStatistics);
+            List<LocalDate> actual = taskService.getNewLocalDate(localDates, oldStatistics);
+            assertEquals(localDate,actual.get(0));
+        }
+    }
+    @Nested
+    class StatsTransportTest{
+        @Test
+        @DisplayName("test statsTransport method when oldStatistics is null")
+        void test1(){
+            doCallRealMethod().when(taskService).statsTransport(user);
+            taskService.statsTransport(user);
+            verify(taskService).statsTransport(any(UserDetail.class),anyList());
+        }
+    }
+    @Nested
+    class statsTransportWithListTest{
+        private List<LocalDate> localDates;
+        private String time;
+        private LocalDate localDate;
+        private MeasurementServiceV2 measurementServiceV2;
+        @BeforeEach
+        void beforeEach(){
+            localDates = new ArrayList<>();
+            localDate = LocalDate.now();
+            localDates.add(localDate);
+            final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMdd");
+            time = localDate.format(format);
+            measurementServiceV2 = mock(MeasurementServiceV2.class);
+            ReflectionTestUtils.setField(taskService,"measurementServiceV2",measurementServiceV2);
+        }
+        @Test
+        void testStatsTransportNormal(){
+            List<TaskDto> allDto = new ArrayList<>();
+            TaskDto dto = new TaskDto();
+            dto.setId(mock(ObjectId.class));
+            allDto.add(dto);
+            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allDto);
+            List<MeasurementEntity> measurementEntities = new ArrayList<>();
+            MeasurementEntity entity = new MeasurementEntity();
+            Map<String, String> map = new HashMap<>();
+            map.put("taskId","111");
+            entity.setTags(map);
+            List<Sample> samples = new ArrayList<>();
+            Sample sample = new Sample();
+            sample.setDate(new Date());
+            Map<String, Number> vs = new HashMap<>();
+            sample.setVs(vs);
+            samples.add(sample);
+            entity.setSamples(samples);
+            measurementEntities.add(entity);
+            when(measurementServiceV2.find(any(Query.class))).thenReturn(measurementEntities);
+            doCallRealMethod().when(taskService).statsTransport(user,localDates);
+            DataFlowInsightStatisticsDto actual = taskService.statsTransport(user, localDates);
+            assertNotEquals(null,actual);
+        }
+    }
+    @Nested
+    class GetByConIdOfTargetNodeTest{
+        List<String> connectionIds;
+        String status;
+        String position;
+        int page;
+        int pageSize;
+        @Test
+        @DisplayName("test getByConIdOfTargetNode method for source")
+        void test1(){
+            connectionIds = new ArrayList<>();
+            connectionIds.add("111");
+            status = "running";
+            position = "source";
+            List<TaskDto> allTasks = new ArrayList<>();
+            TaskDto dto = new TaskDto();
+            DAG dag = mock(DAG.class);
+            List<Node> source = new ArrayList<>();
+            Node node = new TableNode();
+            ((DataParentNode)node).setConnectionId("111");
+            source.add(node);
+            when(dag.getSources()).thenReturn(source);
+            dto.setDag(dag);
+            allTasks.add(dto);
+            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allTasks);
+            doCallRealMethod().when(taskService).getByConIdOfTargetNode(connectionIds,status,position,user,page,pageSize);
+            Map<String, List<TaskDto>> actual = taskService.getByConIdOfTargetNode(connectionIds, status, position, user, page, pageSize);
+            assertEquals(allTasks,actual.get("111"));
+        }
+        @Test
+        @DisplayName("test getByConIdOfTargetNode method for target")
+        void test2(){
+            connectionIds = new ArrayList<>();
+            connectionIds.add("111");
+            position = "target";
+            List<TaskDto> allTasks = new ArrayList<>();
+            TaskDto dto = new TaskDto();
+            DAG dag = mock(DAG.class);
+            List<Node> target = new ArrayList<>();
+            Node node = new TableNode();
+            ((DataParentNode)node).setConnectionId("111");
+            target.add(node);
+            when(dag.getTargets()).thenReturn(target);
+            dto.setDag(dag);
+            allTasks.add(dto);
+            doCallRealMethod().when(taskService).getByConIdOfTargetNode(connectionIds,status,position,user,page,pageSize);
+            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allTasks);
+            Map<String, List<TaskDto>> actual = taskService.getByConIdOfTargetNode(connectionIds, status, position, user, page, pageSize);
+            assertEquals(allTasks,actual.get("111"));
+        }
+    }
+    @Nested
+    class FindByConIdTest{
+        private String sourceConnectionId;
+        private String targetConnectionId;
+        private String syncType;
+        private String status;
+        private Where where;
+        private TaskRepository repository;
+        @BeforeEach
+        void beforeEach(){
+            where = new Where();
+            repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            when(repository.whereToCriteria(where)).thenReturn(new Criteria());
+        }
+        @Test
+        void testFindByConIdTest(){
+            sourceConnectionId = "111";
+            targetConnectionId = "222";
+            syncType = "migrate";
+            status = "running";
+            List<TaskDto> tasks = new ArrayList<>();
+            TaskDto dto = new TaskDto();
+            DAG dag = mock(DAG.class);
+            List<Node> source = new ArrayList<>();
+            source.add(new TableNode());
+            when(dag.getSources()).thenReturn(source);
+            List<Node> target = new ArrayList<>();
+            Node node = new TableNode();
+            ((DataParentNode)node).setConnectionId("222");
+            target.add(node);
+            when(dag.getTargets()).thenReturn(target);
+            dto.setDag(dag);
+            dto.setId(mock(ObjectId.class));
+            tasks.add(dto);
+            LinkedList<Edge> edges = new LinkedList<>();
+            edges.add(mock(Edge.class));
+            when(dag.getEdges()).thenReturn(edges);
+            doReturn(tasks).when(taskService).findAllDto(any(Query.class),any(UserDetail.class));
+            taskService.findByConId(sourceConnectionId,targetConnectionId,syncType,status,where,user);
+        }
+    }
+    @Nested
+    class importRmProjectTest {
+        ParseRelMigFile parseRelMigFile;
+        TaskRepository taskRepository = mock(TaskRepository.class);
+        TaskServiceImpl taskService = spy(new TaskServiceImpl(taskRepository));
+        UserDetail userDetail;
+        FileInputStream fileInputStream;
+        MockMultipartFile mockMultipartFile;
+        String rmJson;
+
+        @BeforeEach
+        void beforeEach() throws Exception {
+            userDetail = new UserDetail("6393f084c162f518b18165c3", "customerId", "username", "password", "customerType",
+                    "accessCode", false, false, false, false, Arrays.asList(new SimpleGrantedAuthority("role")));
+            URL resource = this.getClass().getClassLoader().getResource("test.relmig");
+            fileInputStream = new FileInputStream(resource.getFile());
+            mockMultipartFile = new MockMultipartFile("test.relmig", fileInputStream);
+            rmJson = new String(mockMultipartFile.getBytes());
+        }
+
+        @Test
+        void importRmProjectTest() throws IOException {
+
+            HashMap<String, Object> rmProject = new ObjectMapper().readValue(rmJson, HashMap.class);
+            HashMap<String, Object> project = (HashMap<String, Object>) rmProject.get("project");
+            HashMap<String, Object> content = (HashMap<String, Object>) project.get("content");
+            HashMap<String, Object> contentCollections = (HashMap<String, Object>) content.get("collections");
+            ParseParam param = new ParseParam()
+                    .withMultipartFile(mockMultipartFile)
+                    .withSink("sink")
+                    .withSource("source")
+                    .withUser(userDetail);
+            param.setRelMigStr(rmJson);
+            param.setRelMigInfo(rmProject);
+            parseRelMigFile = new ParseRelMigFile(param) {
+                @Override
+                public List<TaskDto> parse() {
+                    return null;
+                }
+            };
+            Map<String, String> stringStringMap = parseRelMigFile.doParse("sourceConnectionId", "targetConnectionId", userDetail);
+            TaskDto taskDto = null;
+            for (String taskKey : stringStringMap.keySet()) {
+                taskDto = JsonUtil.parseJsonUseJackson(stringStringMap.get(taskKey), TaskDto.class);
+            }
+            assertEquals(5, stringStringMap.size());
+        }
+
+        @Test
+        void nullImportRmProjectTest() {
+            ParseParam param = new ParseParam()
+                    .withMultipartFile(mockMultipartFile)
+                    .withSink("sink")
+                    .withSource("source")
+                    .withUser(userDetail);
+            param.setRelMigInfo(new HashMap<>());
+            parseRelMigFile = new ParseRelMigFile(param) {
+                @Override
+                public List<TaskDto> parse() {
+                    return null;
+                }
+            };
+            assertDoesNotThrow(() -> {
+                parseRelMigFile.doParse("sourceConnectionId", "targetConnectionId", userDetail);
+            });
+        }
+
+        @Test
+        void replaceIdTest() throws IOException {
+            Map<String, Object> rmProject = new ObjectMapper().readValue(rmJson, HashMap.class);
+            Map<String, Object> project = (Map<String, Object>) rmProject.get("project");
+            Map<String, Object> content = (Map<String, Object>) project.get("content");
+            Map<String, Object> contentMapping = (Map<String, Object>) content.get("mappings");
+            Map<String, Object> contentCollections = (Map<String, Object>) content.get("collections");
+            Set<String> collectionKeys = contentCollections.keySet();
+            String collectionKey = null;
+            for (String key : collectionKeys) {
+                collectionKey = key;
+            }
+            Set<String> contentMappingKeys = contentMapping.keySet();
+            String contentMappingKey = null;
+            String contentMappingCollectionId = null;
+            for (String key : contentMappingKeys) {
+                Map<String, Object> mapping = (Map<String, Object>) contentMapping.get(key);
+                String collectionId = (String) mapping.get("collectionId");
+                contentMappingCollectionId = collectionId;
+                contentMappingKey = key;
+            }
+            ParseParam param = new ParseParam()
+                    .withMultipartFile(mockMultipartFile)
+                    .withSink("sink")
+                    .withSource("source")
+                    .withUser(userDetail);
+            param.setRelMigInfo(new HashMap<>());
+            parseRelMigFile = new ParseRelMigFile(param) {
+                @Override
+                public List<TaskDto> parse() {
+                    return null;
+                }
+            };
+            parseRelMigFile.replaceRmProjectId();
+            Set<String> afterStrings = contentCollections.keySet();
+            String afterCollectionKey = null;
+            for (String afterKey1 : afterStrings) {
+                afterCollectionKey = afterKey1;
+            }
+            Set<String> afterContentMappingKeys = contentMapping.keySet();
+            String afterContentMappingCollectionId = null;
+            String afterContentMappingKey = null;
+            for (String key : afterContentMappingKeys) {
+                Map<String, Object> mapping = (Map<String, Object>) contentMapping.get(key);
+                afterContentMappingCollectionId = (String) mapping.get("collectionId");
+                afterContentMappingKey = key;
+            }
+            assertEquals(collectionKey, afterCollectionKey);
+            assertEquals(contentMappingKey, afterContentMappingKey);
+            assertEquals(contentMappingCollectionId, afterContentMappingCollectionId);
+        }
+
+        @Test
+        void testReplaceRelationShipsKey() throws IOException {
+            Map<String, String> globalIdMap = new HashMap<>();
+            Map<String, Object> rmProject = new ObjectMapper().readValue(rmJson, HashMap.class);
+            Map<String, Object> project = (Map<String, Object>) rmProject.get("project");
+            Map<String, Object> content = (Map<String, Object>) project.get("content");
+            Map<String, Object> relationships = content.get("relationships") == null ? new HashMap<>() : (Map<String, Object>) content.get("relationships");
+            Map<String, Object> collectionMap = (Map<String, Object>) relationships.get("collections");
+            Map<String, Object> mappingsMap = (Map<String, Object>) relationships.get("mappings");
+            String collectionKey = null;
+            for (String key : collectionMap.keySet()) {
+                collectionKey = key;
+            }
+            String mappingKey = null;
+            for (String key : mappingsMap.keySet()) {
+                mappingKey = key;
+            }
+            String relationShipMappingsKey = null;
+            for (String key : mappingsMap.keySet()) {
+                relationShipMappingsKey = key;
+            }
+            ParseParam param = new ParseParam()
+                    .withMultipartFile(mockMultipartFile)
+                    .withSink("sink")
+                    .withSource("source")
+                    .withUser(userDetail);
+            param.setRelMigStr(rmJson);
+            param.setRelMigInfo(rmProject);
+            parseRelMigFile = new ParseRelMigFile(param) {
+                @Override
+                public List<TaskDto> parse() {
+                    return null;
+                }
+            };
+            parseRelMigFile.replaceRelationShipsKey(globalIdMap, content);
+            String afterCollectionKey = null;
+            for (String key : collectionMap.keySet()) {
+                afterCollectionKey = key;
+            }
+            String afterMappingKey = null;
+            for (String key : mappingsMap.keySet()) {
+                afterMappingKey = key;
+            }
+            String afterRelationShipMappingsKey = null;
+            for (String key : mappingsMap.keySet()) {
+                afterRelationShipMappingsKey = key;
+            }
+            assertNotEquals(collectionKey, afterCollectionKey);
+            assertNotEquals(afterMappingKey, mappingKey);
+            assertNotEquals(afterRelationShipMappingsKey, relationShipMappingsKey);
+        }
+
+        @Test
+        void testImportRmProject() throws IOException {
+            CustomSqlService customSqlService = mock(CustomSqlService.class);
+            taskService.setCustomSqlService(customSqlService);
+            DateNodeService dataNodeService = mock(DateNodeService.class);
+            taskService.setDateNodeService(dataNodeService);
+            ParseParam param = new ParseParam()
+                    .withMultipartFile(mockMultipartFile)
+                    .withSink("sink")
+                    .withSource("source")
+                    .withUser(userDetail);
+            param.setRelMigStr(rmJson);
+            param.setRelMigInfo(new ObjectMapper().readValue(rmJson, HashMap.class));
+            parseRelMigFile = new ParseRelMigFile(param) {
+                @Override
+                public List<TaskDto> parse() {
+                    return null;
+                }
+            };
+            Map<String, String> stringStringMap = parseRelMigFile.doParse("sourceConnectionId", "targetConnectionId", userDetail);
+            TaskDto taskDto = null;
+            for (String s1 : stringStringMap.keySet()) {
+                taskDto = JsonUtil.parseJsonUseJackson(stringStringMap.get(s1), TaskDto.class);
+            }
+            try (MockedStatic<BeanUtils> beanUtilsMockedStatic = mockStatic(BeanUtils.class); MockedStatic<DataPermissionHelper> dataPermissionHelperMockedStatic = mockStatic(DataPermissionHelper.class)) {
+                BeanUtils.copyProperties(any(), any());
+                TaskEntity taskEntity = taskService.convertToEntity(TaskEntity.class, taskDto);
+                when(taskRepository.importEntity(any(), any())).thenReturn(taskEntity);
+                MongoTemplate mongoTemplate = mock(MongoTemplate.class);
+                when(taskRepository.getMongoOperations()).thenReturn(mongoTemplate);
+                assertThrows(BizException.class, () -> {
+                    taskService.importRmProject(mockMultipartFile, userDetail, false, new ArrayList<>(), "123", "123");
+                });
+            }
+        }
+
+        @Test
+        void testGenProperties() throws IOException {
+            URL resource = this.getClass().getClassLoader().getResource("EmployeeSchema.relmig");
+            FileInputStream fileInputStream = new FileInputStream(resource.getFile());
+            MockMultipartFile mockMultipartFile = new MockMultipartFile("EmployeeSchema.relmig", fileInputStream);
+            String s = new String(mockMultipartFile.getBytes());
+            ParseParam param = new ParseParam()
+                    .withMultipartFile(mockMultipartFile)
+                    .withSink("sink")
+                    .withSource("source")
+                    .withUser(userDetail);
+            param.setRelMigStr(s);
+            param.setRelMigInfo(new ObjectMapper().readValue(s, HashMap.class));
+            parseRelMigFile = new ParseRelMigFile(param) {
+                @Override
+                public List<TaskDto> parse() {
+                    return null;
+                }
+            };
+            Map<String, String> stringStringMap = parseRelMigFile.doParse("sourceConnectionId", "targetConnectionId", userDetail);
+            TaskDto taskDto = null;
+            for (String key : stringStringMap.keySet()) {
+                System.out.println();
+                taskDto = JsonUtil.parseJsonUseJackson(stringStringMap.get(key), TaskDto.class);
+            }
+            ;
+            List<Node> nodes = taskDto.getDag().getNodes();
+            boolean flag = false;
+            for (Node node : nodes) {
+                if (node.getType().equals("merge_table_processor")) {
+                    flag = true;
+                }
+            }
+            assertTrue(flag);
+        }
+    }
+    @Nested
+    class ReNewNotSendMqTest{
+        TaskRepository taskRepository=mock(TaskRepository.class);
+        TaskServiceImpl taskService=spy(new TaskServiceImpl(taskRepository));
+        @DisplayName("test errorEvents is null")
+        @Test
+        void test(){
+            TaskDto taskDto = new TaskDto();
+            taskDto.setDag(mock(DAG.class));
+            UpdateResult updateResult = new UpdateResult() {
+                @Override
+                public boolean wasAcknowledged() {
+                    return false;
+                }
+
+                @Override
+                public long getMatchedCount() {
+                    return 0;
+                }
+
+                @Override
+                public long getModifiedCount() {
+                    return 0;
+                }
+
+                @Override
+                public BsonValue getUpsertedId() {
+                    return null;
+                }
+            };
+            when(taskRepository.update(any(Query.class),any(Update.class),any())).thenAnswer(invocationOnMock -> {
+                Update update = invocationOnMock.getArgument(1);
+                Assertions.assertNull(update.getUpdateObject().get("errorEvents"));
+                return updateResult;
+            });
+            taskService.renewNotSendMq(taskDto,mock(UserDetail.class));
+        }
+    }
+    @Nested
+    class IncreaseClearTest{
+        private ObjectId taskId;
+        private String srcNode;
+        private String tgtNode;
+        @Test
+        @DisplayName("test increaseClear method normal")
+        void test(){
+            taskId = mock(ObjectId.class);
+            srcNode = "111";
+            tgtNode = "222";
+            TaskDto dto = mock(TaskDto.class);
+            when(taskService.checkExistById(taskId,user,"attrs")).thenReturn(dto);
+            doCallRealMethod().when(taskService).increaseClear(taskId,srcNode,tgtNode,user);
+            taskService.increaseClear(taskId,srcNode,tgtNode,user);
+            verify(taskService).clear(srcNode,tgtNode,user,dto);
+        }
+    }
+    @Nested
+    class ClearTest{
+        private String srcNode;
+        private String tgtNode;
+        private TaskDto dto;
+        private TaskRepository repository;
+        @BeforeEach
+        void beforeEach(){
+            srcNode = "111";
+            tgtNode = "111";
+            dto = new TaskDto();
+            repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+        }
+        @Test
+        @DisplayName("test clear method when syncProgress is null")
+        void test1(){
+            Map<String, Object> attrs = new HashMap<>();
+            dto.setAttrs(attrs);
+            taskService.clear(srcNode,tgtNode,user,dto);
+            verify(repository,new Times(0)).updateFirst(any(Query.class),any(Update.class),any(UserDetail.class));
+        }
+        @Test
+        @DisplayName("test clear method normal")
+        void test2(){
+            Map<String, Object> attrs = new HashMap<>();
+            Map syncProgressMap = new HashMap();
+            syncProgressMap.put("111","test1");
+            syncProgressMap.put("222","test2");
+            attrs.put("syncProgress",syncProgressMap);
+            dto.setAttrs(attrs);
+            dto.setId(mock(ObjectId.class));
+            taskService.clear(srcNode,tgtNode,user,dto);
+            verify(repository,new Times(1)).updateFirst(any(Query.class),any(Update.class),any(UserDetail.class));
+        }
+    }
+    @Nested
+    class IncreaseBacktrackingTest{
+        private ObjectId taskId;
+        private String srcNode;
+        private String tgtNode;
+        private TaskDto.SyncPoint point;
+        private DAG dag;
+        private TaskDto dto;
+        private ObjectId id;
+        @BeforeEach
+        void beforeEach(){
+            taskId = mock(ObjectId.class);
+            srcNode = "111";
+            tgtNode = "222";
+            point = mock(TaskDto.SyncPoint.class);
+            dto = mock(TaskDto.class);
+            when(taskService.checkExistById(taskId, user, "parentId", "attrs", "dag", "syncPoints")).thenReturn(dto);
+            dag = mock(DAG.class);
+            when(dto.getDag()).thenReturn(dag);
+            id = mock(ObjectId.class);
+            when(dto.getId()).thenReturn(id);
+        }
+        @Test
+        @DisplayName("test increaseBacktracking method when sync points is empty")
+        void test1(){
+            try (MockedStatic<Criteria> mb = Mockito
+                    .mockStatic(Criteria.class)) {
+                Criteria criteria = mock(Criteria.class);
+                mb.when(()->Criteria.where(anyString())).thenReturn(criteria);
+                when(criteria.is(id)).thenReturn(mock(Criteria.class));
+                when(dag.getNode(tgtNode)).thenReturn(mock(DataParentNode.class));
+                List<TaskDto.SyncPoint> syncPoints = new ArrayList<>();
+                when(dto.getSyncPoints()).thenReturn(syncPoints);
+                doCallRealMethod().when(taskService).increaseBacktracking(taskId,srcNode,tgtNode,point,user);
+                taskService.increaseBacktracking(taskId,srcNode,tgtNode,point,user);
+                verify(criteria,new Times(0)).elemMatch(any(Criteria.class));
+            }
+        }
+        @Test
+        @DisplayName("test increaseBacktracking method for when sync points is not empty")
+        void test2(){
+            try (MockedStatic<Criteria> mb = Mockito
+                    .mockStatic(Criteria.class)) {
+                Criteria criteria = mock(Criteria.class);
+                mb.when(()->Criteria.where(anyString())).thenReturn(criteria);
+                Criteria mock = mock(Criteria.class);
+                when(criteria.is(any())).thenReturn(mock);
+                when(mock.and(anyString())).thenReturn(mock);
+                when(mock.elemMatch(any(Criteria.class))).thenReturn(mock);
+                DataParentNode node = mock(DataParentNode.class);
+                when(node.getId()).thenReturn("111");
+                when(dag.getNode(tgtNode)).thenReturn(node);
+                List<TaskDto.SyncPoint> syncPoints = new ArrayList<>();
+                TaskDto.SyncPoint item1 = mock(TaskDto.SyncPoint.class);
+                syncPoints.add(item1);
+                when(item1.getNodeId()).thenReturn("222");
+                TaskDto.SyncPoint item2 = mock(TaskDto.SyncPoint.class);
+                syncPoints.add(item2);
+                when(item1.getNodeId()).thenReturn("111");
+                when(dto.getSyncPoints()).thenReturn(syncPoints);
+                doCallRealMethod().when(taskService).increaseBacktracking(taskId,srcNode,tgtNode,point,user);
+                taskService.increaseBacktracking(taskId,srcNode,tgtNode,point,user);
+                verify(mock,new Times(1)).elemMatch(any(Criteria.class));
+            }
+        }
+    }
+    @Nested
+    class StartPlanMigrateDagTask{
+        private UserService userService;
+        private StateMachineService stateMachineService;
+        private MonitoringLogsService monitoringLogsService;
+        @BeforeEach
+        void beforeEach(){
+            List<TaskDto> taskList = new ArrayList<>();
+            TaskDto dto = mock(TaskDto.class);
+            taskList.add(dto);
+            when(dto.getTransformed()).thenReturn(true);
+            when(dto.getId()).thenReturn(mock(ObjectId.class));
+            when(dto.getUserId()).thenReturn("111");
+            when(taskService.findAll(any(Query.class))).thenReturn(taskList);
+            userService = mock(UserService.class);
+            stateMachineService = mock(StateMachineService.class);
+            monitoringLogsService = mock(MonitoringLogsService.class);
+            ReflectionTestUtils.setField(taskService,"userService",userService);
+            ReflectionTestUtils.setField(taskService,"stateMachineService",stateMachineService);
+            ReflectionTestUtils.setField(taskService,"monitoringLogsService",monitoringLogsService);
+        }
+        @Test
+        @DisplayName("test startPlanMigrateDagTask method when task list is empty")
+        void test1(){
+            when(taskService.findAll(any(Query.class))).thenReturn(null);
+            doCallRealMethod().when(taskService).startPlanMigrateDagTask();
+            taskService.startPlanMigrateDagTask();
+            verify(userService,new Times(0)).getUserByIdList(anyList());
+        }
+        @Test
+        @DisplayName("test startPlanMigrateDagTask method normal")
+        void test2(){
+            List<UserDetail> userList = new ArrayList<>();
+            UserDetail userDetail = mock(UserDetail.class);
+            userList.add(userDetail);
+            when(userDetail.getUserId()).thenReturn("111");
+            when(userService.getUserByIdList(anyList())).thenReturn(userList);
+            doCallRealMethod().when(taskService).startPlanMigrateDagTask();
+            taskService.startPlanMigrateDagTask();
+            verify(taskService,new Times(1)).updateById(any(ObjectId.class),any(Update.class),any(UserDetail.class));
+        }
+        @Test
+        @DisplayName("test startPlanMigrateDagTask method for TapCodeException")
+        void test3(){
+            List<UserDetail> userList = new ArrayList<>();
+            UserDetail userDetail = mock(UserDetail.class);
+            userList.add(userDetail);
+            when(userDetail.getUserId()).thenReturn("111");
+            when(userService.getUserByIdList(anyList())).thenReturn(userList);
+            doThrow(TapCodeException.class).when(taskService).start(any(TaskDto.class),any(UserDetail.class),anyString());
+            doCallRealMethod().when(taskService).startPlanMigrateDagTask();
+            taskService.startPlanMigrateDagTask();
+            verify(monitoringLogsService,new Times(1)).startTaskErrorStackTrace(any(TaskDto.class),any(UserDetail.class),any(Throwable.class),any(Level.class));
+        }
+        @Test
+        @DisplayName("test startPlanMigrateDagTask method for other exception")
+        void test4(){
+            List<UserDetail> userList = new ArrayList<>();
+            UserDetail userDetail = mock(UserDetail.class);
+            userList.add(userDetail);
+            when(userDetail.getUserId()).thenReturn("111");
+            when(userService.getUserByIdList(anyList())).thenReturn(userList);
+            doThrow(RuntimeException.class).when(taskService).start(any(TaskDto.class),any(UserDetail.class),anyString());
+            doCallRealMethod().when(taskService).startPlanMigrateDagTask();
+            taskService.startPlanMigrateDagTask();
+            verify(monitoringLogsService,new Times(1)).startTaskErrorStackTrace(any(TaskDto.class),any(UserDetail.class),any(Throwable.class),any(Level.class));
+        }
+    }
+    @Nested
+    class StartPlanCronTaskTest{
+        private ScheduleService scheduleService;
+        @BeforeEach
+        void beforeEach(){
+            List<TaskDto> taskList = new ArrayList<>();
+            TaskDto dto = mock(TaskDto.class);
+            taskList.add(dto);
+            when(dto.getTransformed()).thenReturn(true);
+            when(dto.getId()).thenReturn(mock(ObjectId.class));
+            when(dto.getUserId()).thenReturn("111");
+            when(taskService.findAll(any(Query.class))).thenReturn(taskList);
+            scheduleService = mock(ScheduleService.class);
+            ReflectionTestUtils.setField(taskService,"scheduleService",scheduleService);
+        }
+        @Test
+        @DisplayName("test startPlanCronTask method normal")
+        void test1(){
+            doCallRealMethod().when(taskService).startPlanCronTask();
+            taskService.startPlanCronTask();
+            verify(scheduleService).executeTask(any(TaskDto.class));
+        }
+        @Test
+        @DisplayName("test startPlanCronTask method with exception")
+        void test2(){
+            doThrow(RuntimeException.class).when(scheduleService).executeTask(any(TaskDto.class));
+            doCallRealMethod().when(taskService).startPlanCronTask();
+            taskService.startPlanCronTask();
+            verify(scheduleService).executeTask(any(TaskDto.class));
+        }
+    }
+    @Nested
+    class UpdateDagTest{
+        @Test
+        void testUpdateDagNormal(){
+            TaskUpdateDagService taskUpdateDagService = mock(TaskUpdateDagService.class);
+            ReflectionTestUtils.setField(taskService,"taskUpdateDagService",taskUpdateDagService);
+            boolean saveHistory = false;
+            ObjectId id = mock(ObjectId.class);
+            when(taskDto.getId()).thenReturn(id);
+            TaskDto oldTask = mock(TaskDto.class);
+            when(taskService.checkExistById(id,user)).thenReturn(oldTask);
+            doCallRealMethod().when(taskService).updateDag(taskDto,user,saveHistory);
+            taskService.updateDag(taskDto,user,saveHistory);
+            verify(taskUpdateDagService).updateDag(taskDto,oldTask,user,saveHistory);
+        }
+    }
+    @Nested
+    class FindByVersionTime{
+        private String id;
+        private Long time;
+        private TaskRepository repository;
+        @BeforeEach
+        void beforeEach(){
+            id = "662877df9179877be8b37074";
+            time = 1713846744L;
+            repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+        }
+        @Test
+        @DisplayName("test findByVersionTime method when dDlTaskHistories is null")
+        void test1(){
+            MongoTemplate template = mock(MongoTemplate.class);
+            when(repository.getMongoOperations()).thenReturn(template);
+            when(template.findOne(any(Query.class),any(Class.class),anyString())).thenReturn(null);
+            TaskDto dto = mock(TaskDto.class);
+            doReturn(dto).when(taskService).findById(any(ObjectId.class));
+            TaskDto actual = taskService.findByVersionTime(id, time);
+            assertEquals(dto,actual);
+        }
+        @Test
+        @DisplayName("test findByVersionTime method when dDlTaskHistories is not null")
+        void test2(){
+            MongoTemplate template = mock(MongoTemplate.class);
+            when(repository.getMongoOperations()).thenReturn(template);
+            TaskDto dto = new TaskDto();
+            when(template.findOne(any(Query.class),any(Class.class),anyString())).thenReturn(dto);
+            TaskDto actual = taskService.findByVersionTime(id, time);
+            assertEquals(id,actual.getId().toHexString());
+        }
+    }
+    @Nested
+    class CleanTest{
+        private MetaDataHistoryService historyService;
+        private TaskRepository repository;
+        @BeforeEach
+        void beforeEach(){
+            repository = mock(TaskRepository.class);
+            taskService = spy(new TaskServiceImpl(repository));
+            historyService = mock(MetaDataHistoryService.class);
+            ReflectionTestUtils.setField(taskService,"historyService",historyService);
+        }
+        @Test
+        void testCleanNormal(){
+            String taskId = "111";
+            Long time = 1713846744L;
+            when(repository.getMongoOperations()).thenReturn(mock(MongoTemplate.class));
+            taskService.clean(taskId,time);
+            verify(historyService).clean(taskId,time);
+        }
+    }
+    @Nested
+    class TotalAutoInspectResultsDiffTablesTest{
+        private IdParam param;
+        private TaskAutoInspectResultsService taskAutoInspectResultsService;
+        private String taskId;
+        @BeforeEach
+        void beforeEach(){
+            param = new IdParam();
+            taskId = "662877df9179877be8b37074";
+            param.setId(taskId);
+            taskAutoInspectResultsService = mock(TaskAutoInspectResultsService.class);
+            ReflectionTestUtils.setField(taskService,"taskAutoInspectResultsService",taskAutoInspectResultsService);
+        }
+        @Test
+        @DisplayName("test totalAutoInspectResultsDiffTables method when taskDto is null")
+        void test1(){
+            doCallRealMethod().when(taskService).totalAutoInspectResultsDiffTables(param);
+            Map<String, Object> actual = taskService.totalAutoInspectResultsDiffTables(param);
+            assertEquals(0,actual.size());
+        }
+        @Test
+        @DisplayName("test totalAutoInspectResultsDiffTables method when progress is null")
+        void test2(){
+            TaskDto taskDto = new TaskDto();
+            when(taskService.findByTaskId(new ObjectId(taskId), AutoInspectConstants.AUTO_INSPECT_PROGRESS_PATH)).thenReturn(taskDto);
+            doCallRealMethod().when(taskService).totalAutoInspectResultsDiffTables(param);
+            Map<String, Object> actual = taskService.totalAutoInspectResultsDiffTables(param);
+            assertEquals(0,actual.size());
+        }
+        @Test
+        @DisplayName("test totalAutoInspectResultsDiffTables method when map is null")
+        void test3(){
+            TaskDto taskDto = new TaskDto();
+            Map<String, Object> attr = new HashMap<>();
+            attr.put("autoInspectProgress",mock(AutoInspectProgress.class));
+            taskDto.setAttrs(attr);
+            when(taskService.findByTaskId(new ObjectId(taskId), AutoInspectConstants.AUTO_INSPECT_PROGRESS_PATH)).thenReturn(taskDto);
+            when(taskAutoInspectResultsService.totalDiffTables(taskId)).thenReturn(null);
+            doCallRealMethod().when(taskService).totalAutoInspectResultsDiffTables(param);
+            Map<String, Object> actual = taskService.totalAutoInspectResultsDiffTables(param);
+            assertEquals(2,actual.size());
+        }
+        @Test
+        @DisplayName("test totalAutoInspectResultsDiffTables method normal")
+        void test4(){
+            TaskDto taskDto = new TaskDto();
+            Map<String, Object> attr = new HashMap<>();
+            attr.put("autoInspectProgress",mock(AutoInspectProgress.class));
+            taskDto.setAttrs(attr);
+            when(taskService.findByTaskId(new ObjectId(taskId), AutoInspectConstants.AUTO_INSPECT_PROGRESS_PATH)).thenReturn(taskDto);
+            Map<String, Object> map = new HashMap<>();
+            when(taskAutoInspectResultsService.totalDiffTables(taskId)).thenReturn(map);
+            doCallRealMethod().when(taskService).totalAutoInspectResultsDiffTables(param);
+            Map<String, Object> actual = taskService.totalAutoInspectResultsDiffTables(param);
+            assertEquals(4,actual.size());
+        }
+    }
+    @Nested
+    class UpdateTaskLogSettingTest{
+        private String taskId;
+        private LogSettingParam logSettingParam;
+        @BeforeEach
+        void beforeEach(){
+            taskId = "65bc933c6129fe73d7858b40";
+            logSettingParam = new LogSettingParam();
+        }
+        @Test
+        @DisplayName("test updateTaskLogSetting method when task is null")
+        void test1(){
+            when(taskService.findById(new ObjectId(taskId))).thenReturn(null);
+            doCallRealMethod().when(taskService).updateTaskLogSetting(taskId,logSettingParam,user);
+            assertThrows(BizException.class,()->taskService.updateTaskLogSetting(taskId,logSettingParam,user));
+        }
+        @Test
+        @DisplayName("test updateTaskLogSetting method when level equals debug")
+        void test2(){
+            TaskDto task = new TaskDto();
+            Map<String, Object> logSetting = new HashMap<>();
+            task.setLogSetting(logSetting);
+            logSettingParam.setLevel("debug");
+            when(taskService.findById(new ObjectId(taskId))).thenReturn(task);
+            doCallRealMethod().when(taskService).updateTaskLogSetting(taskId,logSettingParam,user);
+            taskService.updateTaskLogSetting(taskId,logSettingParam,user);
+            verify(taskService).updateById(any(ObjectId.class),any(Update.class),any(UserDetail.class));
+        }
+        @Test
+        @DisplayName("test updateTaskLogSetting method when level not equals debug")
+        void test3(){
+            TaskDto task = new TaskDto();
+            logSettingParam.setLevel("info");
+            when(taskService.findById(new ObjectId(taskId))).thenReturn(task);
+            doCallRealMethod().when(taskService).updateTaskLogSetting(taskId,logSettingParam,user);
+            taskService.updateTaskLogSetting(taskId,logSettingParam,user);
+            verify(taskService).updateById(any(ObjectId.class),any(Update.class),any(UserDetail.class));
+        }
+    }
+    @Nested
+    class Chart6Test{
+        private MeasurementServiceV2 measurementServiceV2;
+        @BeforeEach
+        void beforeEach(){
+            measurementServiceV2 = mock(MeasurementServiceV2.class);
+            ReflectionTestUtils.setField(taskService,"measurementServiceV2",measurementServiceV2);
+        }
+        @Test
+        @DisplayName("test chart6 method normal")
+        void test1(){
+            List<TaskDto> allDto = new ArrayList<>();
+            TaskDto task = new TaskDto();
+            String id = "65bc933c6129fe73d7858b40";
+            task.setId(new ObjectId(id));
+            allDto.add(task);
+            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allDto);
+            MeasurementEntity measurement = new MeasurementEntity();
+            List<Sample> samples = new ArrayList<>();
+            Sample sample = new Sample();
+            sample.setDate(new Date());
+            Map<String, Number> vs = new HashMap<>();
+            vs.put("inputInsertTotal",1);
+            vs.put("outputInsertTotal",1);
+            vs.put("inputUpdateTotal",1);
+            vs.put("inputDeleteTotal",1);
+            sample.setVs(vs);
+            samples.add(sample);
+            measurement.setSamples(samples);
+            when(measurementServiceV2.findLastMinuteByTaskId(id)).thenReturn(measurement);
+            doCallRealMethod().when(taskService).chart6(user);
+            Chart6Vo actual = taskService.chart6(user);
+            assertNotEquals(3,actual.getInputTotal());
+            assertNotEquals(1,actual.getInsertedTotal());
+            assertNotEquals(1,actual.getOutputTotal());
+            assertNotEquals(1,actual.getUpdatedTotal());
+            assertNotEquals(1,actual.getDeletedTotal());
+        }
+        @Test
+        @DisplayName("test chart6 method when ids is empty")
+        void test2(){
+            List<TaskDto> allDto = new ArrayList<>();
+            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allDto);
+            doCallRealMethod().when(taskService).chart6(user);
+            Chart6Vo actual = taskService.chart6(user);
+            assertNotEquals(0,actual.getInputTotal());
+            assertNotEquals(0,actual.getInsertedTotal());
+            assertNotEquals(0,actual.getOutputTotal());
+            assertNotEquals(0,actual.getUpdatedTotal());
+            assertNotEquals(0,actual.getDeletedTotal());
+        }
+        @Test
+        @DisplayName("test chart6 method when measurement is null")
+        void test3(){
+            List<TaskDto> allDto = new ArrayList<>();
+            TaskDto task = new TaskDto();
+            String id = "65bc933c6129fe73d7858b40";
+            task.setId(new ObjectId(id));
+            allDto.add(task);
+            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allDto);
+            when(measurementServiceV2.findLastMinuteByTaskId(id)).thenReturn(null);
+            doCallRealMethod().when(taskService).chart6(user);
+            Chart6Vo actual = taskService.chart6(user);
+            assertNotEquals(0,actual.getInputTotal());
+            assertNotEquals(0,actual.getInsertedTotal());
+            assertNotEquals(0,actual.getOutputTotal());
+            assertNotEquals(0,actual.getUpdatedTotal());
+            assertNotEquals(0,actual.getDeletedTotal());
+        }
+        @Test
+        @DisplayName("test chart6 method when samples is empty")
+        void test4(){
+            List<TaskDto> allDto = new ArrayList<>();
+            TaskDto task = new TaskDto();
+            String id = "65bc933c6129fe73d7858b40";
+            task.setId(new ObjectId(id));
+            allDto.add(task);
+            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allDto);
+            MeasurementEntity measurement = new MeasurementEntity();
+            when(measurementServiceV2.findLastMinuteByTaskId(id)).thenReturn(measurement);
+            doCallRealMethod().when(taskService).chart6(user);
+            Chart6Vo actual = taskService.chart6(user);
+            assertNotEquals(0,actual.getInputTotal());
+            assertNotEquals(0,actual.getInsertedTotal());
+            assertNotEquals(0,actual.getOutputTotal());
+            assertNotEquals(0,actual.getUpdatedTotal());
+            assertNotEquals(0,actual.getDeletedTotal());
+        }
+    }
+    @Nested
+    class StopTaskIfNeedByAgentIdTest{
+        @Test
+        void testStopTaskIfNeedByAgentIdNormal(){
+            String agentId = "111";
+            List<TaskDto> needStopTasks = new ArrayList<>();
+            TaskDto task = new TaskDto();
+            ObjectId id = mock(ObjectId.class);
+            task.setId(id);
+            needStopTasks.add(task);
+            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(needStopTasks);
+            doCallRealMethod().when(taskService).stopTaskIfNeedByAgentId(agentId,user);
+            taskService.stopTaskIfNeedByAgentId(agentId,user);
+            verify(taskService).stopped(id,user);
+        }
+    }
+    @Nested
+    class GetTaskStatsByTableNameOrConnectionIdTest{
+        private String connectionId;
+        private String tableName;
+        @Test
+        @DisplayName("test getTaskStatsByTableNameOrConnectionId method when connectionId is blank")
+        void test1(){
+            connectionId = "";
+            doCallRealMethod().when(taskService).getTaskStatsByTableNameOrConnectionId(connectionId,tableName,user);
+            assertThrows(BizException.class,()->taskService.getTaskStatsByTableNameOrConnectionId(connectionId,tableName,user));
+        }
+        @Test
+        @DisplayName("test getTaskStatsByTableNameOrConnectionId method when tableName is blank")
+        void test2(){
+            connectionId = "111";
+            tableName = "";
+            doCallRealMethod().when(taskService).getTaskStatsByTableNameOrConnectionId(connectionId,tableName,user);
+            taskService.getTaskStatsByTableNameOrConnectionId(connectionId,tableName,user);
+            Criteria criteria = new Criteria();
+            criteria.and("dag.nodes.connectionId").is(connectionId).and("is_deleted").ne(true);
+            Query query = Query.query(criteria);
+            verify(taskService).findAllDto(query,user);
+        }
+        @Test
+        @DisplayName("test getTaskStatsByTableNameOrConnectionId method normal")
+        void test3(){
+            connectionId = "111";
+            tableName = "table1";
+            doCallRealMethod().when(taskService).getTaskStatsByTableNameOrConnectionId(connectionId,tableName,user);
+            taskService.getTaskStatsByTableNameOrConnectionId(connectionId,tableName,user);
+            Criteria criteria = new Criteria();
+            criteria.and("dag.nodes.connectionId").is(connectionId).and("is_deleted").ne(true);
+            criteria.orOperator(new Criteria().and("dag.nodes.tableName").is(tableName),
+                    new Criteria().and("dag.nodes.syncObjects.objectNames").is(tableName));
+            Query query = Query.query(criteria);
+            verify(taskService).findAllDto(query,user);
+        }
+    }
+    @Nested
+    class GetTableStatusTest{
+        private String connectionId;
+        private String tableName;
+        private MeasurementServiceV2 measurementServiceV2;
+        @BeforeEach
+        void beforeEach(){
+            measurementServiceV2 = mock(MeasurementServiceV2.class);
+            ReflectionTestUtils.setField(taskService,"measurementServiceV2",measurementServiceV2);
+        }
+        @Test
+        @DisplayName("test getTableStatus method when connectionId is blank")
+        void test1(){
+            connectionId = "";
+            doCallRealMethod().when(taskService).getTableStatus(connectionId,tableName,user);
+            assertThrows(BizException.class,()->taskService.getTableStatus(connectionId,tableName,user));
+        }
+        @Test
+        @DisplayName("test getTableStatus method when tableName is blank")
+        void test2(){
+            connectionId = "111";
+            tableName = "";
+            doCallRealMethod().when(taskService).getTableStatus(connectionId,tableName,user);
+            assertThrows(BizException.class,()->taskService.getTableStatus(connectionId,tableName,user));
+        }
+        @Test
+        @DisplayName("test getTableStatus method when status is running")
+        void test3(){
+            connectionId = "111";
+            tableName = "table1";
+            List<TaskDto> list = new ArrayList<>();
+            TaskDto task = new TaskDto();
+            task.setId(mock(ObjectId.class));
+            task.setStatus("running");
+            list.add(task);
+            when(taskService.findAll(any(Query.class))).thenReturn(list);
+            when(taskService.judgeTargetNode(task,tableName)).thenReturn(true);
+            when(taskService.judgeTargetInspect(connectionId,tableName,user)).thenReturn(true);
+            doCallRealMethod().when(taskService).getTableStatus(connectionId,tableName,user);
+            TableStatusInfoDto actual = taskService.getTableStatus(connectionId, tableName, user);
+            assertEquals(TableStatusEnum.STATUS_NORMAL.getValue(),actual.getStatus());
+        }
+        @Test
+        @DisplayName("test getTableStatus method when status is edit")
+        void test4(){
+            connectionId = "111";
+            tableName = "table1";
+            List<TaskDto> list = new ArrayList<>();
+            TaskDto task = new TaskDto();
+            task.setId(mock(ObjectId.class));
+            task.setStatus("edit");
+            list.add(task);
+            when(taskService.findAll(any(Query.class))).thenReturn(list);
+            when(taskService.judgeTargetNode(task,tableName)).thenReturn(true);
+            when(taskService.judgeTargetInspect(connectionId,tableName,user)).thenReturn(false);
+            doCallRealMethod().when(taskService).getTableStatus(connectionId,tableName,user);
+            TableStatusInfoDto actual = taskService.getTableStatus(connectionId, tableName, user);
+            assertEquals(TableStatusEnum.STATUS_DRAFT.getValue(),actual.getStatus());
+        }
+        @Test
+        @DisplayName("test getTableStatus method when judgeTargetInspect is error")
+        void test5(){
+            connectionId = "111";
+            tableName = "table1";
+            List<TaskDto> list = new ArrayList<>();
+            TaskDto task = new TaskDto();
+            task.setId(mock(ObjectId.class));
+            task.setStatus("running");
+            list.add(task);
+            when(taskService.findAll(any(Query.class))).thenReturn(list);
+            when(taskService.judgeTargetNode(task,tableName)).thenReturn(true);
+            when(taskService.judgeTargetInspect(connectionId,tableName,user)).thenReturn(false);
+            doCallRealMethod().when(taskService).getTableStatus(connectionId,tableName,user);
+            TableStatusInfoDto actual = taskService.getTableStatus(connectionId, tableName, user);
+            assertEquals(TableStatusEnum.STATUS_ERROR.getValue(),actual.getStatus());
+        }
+        @Test
+        @DisplayName("test getTableStatus method when exist is false")
+        void test6(){
+            connectionId = "111";
+            tableName = "table1";
+            doCallRealMethod().when(taskService).getTableStatus(connectionId,tableName,user);
+            TableStatusInfoDto actual = taskService.getTableStatus(connectionId, tableName, user);
+            assertEquals(TableStatusEnum.STATUS_DRAFT.getValue(),actual.getStatus());
+        }
+        @Test
+        @DisplayName("test getTableStatus method when status is error")
+        void test7(){
+            connectionId = "111";
+            tableName = "table1";
+            List<TaskDto> list = new ArrayList<>();
+            TaskDto task = new TaskDto();
+            task.setId(mock(ObjectId.class));
+            task.setStatus("error");
+            list.add(task);
+            when(taskService.findAll(any(Query.class))).thenReturn(list);
+            when(taskService.judgeTargetNode(task,tableName)).thenReturn(true);
+            doCallRealMethod().when(taskService).getTableStatus(connectionId,tableName,user);
+            TableStatusInfoDto actual = taskService.getTableStatus(connectionId, tableName, user);
+            assertEquals(TableStatusEnum.STATUS_ERROR.getValue(),actual.getStatus());
+        }
+    }
+    @Nested
+    class JudgeTargetInspectTest{
+        private String connectionId;
+        private String tableName;
+        private InspectResultService inspectResultService;
+        @BeforeEach
+        void beforeEach(){
+            connectionId = "111";
+            tableName = "table1";
+            inspectResultService = mock(InspectResultService.class);
+            ReflectionTestUtils.setField(taskService,"inspectResultService",inspectResultService);
+        }
+        @Test
+        @DisplayName("test judgeTargetInspect method when inspectResultDto is null")
+        void test1(){
+            doCallRealMethod().when(taskService).judgeTargetInspect(connectionId,tableName,user);
+            boolean actual = taskService.judgeTargetInspect(connectionId, tableName, user);
+            assertEquals(true,actual);
+        }
+        @Test
+        @DisplayName("test judgeTargetInspect method when status is error")
+        void test2(){
+            InspectResultDto inspectResultDto = new InspectResultDto();
+            List<Stats> statsList = new ArrayList<>();
+            Stats stats = new Stats();
+            Source target = new Source();
+            target.setConnectionId("111");
+            target.setTable("table1");
+            stats.setTarget(target);
+            stats.setStatus("ERROR");
+            statsList.add(stats);
+            inspectResultDto.setStats(statsList);
+            when(inspectResultService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(inspectResultDto);
+            doCallRealMethod().when(taskService).judgeTargetInspect(connectionId,tableName,user);
+            boolean actual = taskService.judgeTargetInspect(connectionId, tableName, user);
+            assertEquals(false,actual);
+        }
+        @Test
+        @DisplayName("test judgeTargetInspect method when status is done")
+        void test3(){
+            InspectResultDto inspectResultDto = new InspectResultDto();
+            List<Stats> statsList = new ArrayList<>();
+            Stats stats = new Stats();
+            Source target = new Source();
+            target.setConnectionId("111");
+            target.setTable("table1");
+            stats.setTarget(target);
+            stats.setStatus("DONE");
+            stats.setResult("PASSED");
+            statsList.add(stats);
+            inspectResultDto.setStats(statsList);
+            when(inspectResultService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(inspectResultDto);
+            doCallRealMethod().when(taskService).judgeTargetInspect(connectionId,tableName,user);
+            boolean actual = taskService.judgeTargetInspect(connectionId, tableName, user);
+            assertEquals(true,actual);
+        }
+        @Test
+        @DisplayName("test judgeTargetInspect method when status is running")
+        void test4(){
+            InspectResultDto inspectResultDto = new InspectResultDto();
+            List<Stats> statsList = new ArrayList<>();
+            Stats stats = new Stats();
+            Source target = new Source();
+            target.setConnectionId("111");
+            target.setTable("table1");
+            stats.setTarget(target);
+            stats.setStatus("RUNNING");
+            statsList.add(stats);
+            inspectResultDto.setStats(statsList);
+            when(inspectResultService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(inspectResultDto);
+            doCallRealMethod().when(taskService).judgeTargetInspect(connectionId,tableName,user);
+            boolean actual = taskService.judgeTargetInspect(connectionId, tableName, user);
+            assertEquals(true,actual);
+        }
+        @Test
+        @DisplayName("test judgeTargetInspect method when statsList is empty")
+        void test5(){
+            InspectResultDto inspectResultDto = new InspectResultDto();
+            when(inspectResultService.findOne(any(Query.class),any(UserDetail.class))).thenReturn(inspectResultDto);
+            doCallRealMethod().when(taskService).judgeTargetInspect(connectionId,tableName,user);
+            boolean actual = taskService.judgeTargetInspect(connectionId, tableName, user);
+            assertEquals(false,actual);
+        }
+    }
+    @Nested
+    class JudgeTargetNodeTest{
+        private TaskDto taskDto;
+        private String tableName;
+        @Test
+        @DisplayName("test judgeTargetNode method for databaseNode")
+        void test1(){
+            tableName = "table1";
+            taskDto = new TaskDto();
+            DAG dag = mock(DAG.class);
+            taskDto.setDag(dag);
+            LinkedList<Edge> edges = new LinkedList<>();
+            Edge edge = new Edge();
+            edge.setTarget("111");
+            edges.add(edge);
+            when(dag.getEdges()).thenReturn(edges);
+            List<Node> nodeList = new ArrayList<>();
+            Node node = new DatabaseNode();
+            node.setId("111");
+            nodeList.add(node);
+            DatabaseNode nodeTemp = (DatabaseNode) node;
+            List<SyncObjects> syncObjectsList = new ArrayList<>();
+            SyncObjects syncObjects = new SyncObjects();
+            List<String> objectNames = new ArrayList<>();
+            objectNames.add("table1");
+            syncObjects.setObjectNames(objectNames);
+            syncObjectsList.add(syncObjects);
+            nodeTemp.setSyncObjects(syncObjectsList);
+            when(dag.getNodes()).thenReturn(nodeList);
+            doCallRealMethod().when(taskService).judgeTargetNode(taskDto,tableName);
+            boolean actual = taskService.judgeTargetNode(taskDto, tableName);
+            assertEquals(true,actual);
+        }
+        @Test
+        @DisplayName("test judgeTargetNode method for tableNode")
+        void test2(){
+            tableName = "table1";
+            taskDto = new TaskDto();
+            DAG dag = mock(DAG.class);
+            taskDto.setDag(dag);
+            LinkedList<Edge> edges = new LinkedList<>();
+            Edge edge = new Edge();
+            edge.setTarget("111");
+            edges.add(edge);
+            when(dag.getEdges()).thenReturn(edges);
+            List<Node> nodeList = new ArrayList<>();
+            Node node = new TableNode();
+            node.setId("111");
+            nodeList.add(node);
+            TableNode tableNode = (TableNode) node;
+            tableNode.setTableName("table1");
+            when(dag.getNodes()).thenReturn(nodeList);
+            doCallRealMethod().when(taskService).judgeTargetNode(taskDto,tableName);
+            boolean actual = taskService.judgeTargetNode(taskDto, tableName);
+            assertEquals(true,actual);
+        }
+        @Test
+        @DisplayName("test judgeTargetNode method for jsProcessorNode")
+        void test3(){
+            tableName = "table1";
+            taskDto = new TaskDto();
+            DAG dag = mock(DAG.class);
+            taskDto.setDag(dag);
+            LinkedList<Edge> edges = new LinkedList<>();
+            Edge edge = new Edge();
+            edge.setTarget("111");
+            edges.add(edge);
+            when(dag.getEdges()).thenReturn(edges);
+            List<Node> nodeList = new ArrayList<>();
+            Node node = new JsProcessorNode();
+            node.setId("111");
+            nodeList.add(node);
+            when(dag.getNodes()).thenReturn(nodeList);
+            doCallRealMethod().when(taskService).judgeTargetNode(taskDto,tableName);
+            boolean actual = taskService.judgeTargetNode(taskDto, tableName);
+            assertEquals(false,actual);
+        }
+        @Test
+        @DisplayName("test judgeTargetNode method when edges is empty")
+        void test4(){
+            tableName = "table1";
+            taskDto = new TaskDto();
+            DAG dag = mock(DAG.class);
+            taskDto.setDag(dag);
+            LinkedList<Edge> edges = new LinkedList<>();
+            when(dag.getEdges()).thenReturn(edges);
+            doCallRealMethod().when(taskService).judgeTargetNode(taskDto,tableName);
+            boolean actual = taskService.judgeTargetNode(taskDto, tableName);
+            assertEquals(false,actual);
+        }
+    }
+    @Nested
+    class FindHeartbeatByConnectionIdTest{
+        private String connectionId;
+        private String includeFields;
+        @Test
+        @DisplayName("test findHeartbeatByTaskId method when includeFields is null")
+        void test1(){
+            connectionId = "111";
+            Query query = Query.query(Criteria.where("dag.nodes.connectionId").is(connectionId)
+                    .and("syncType").is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
+                    .and("is_deleted").is(false)
+            );
+            doCallRealMethod().when(taskService).findHeartbeatByConnectionId(connectionId);
+            taskService.findHeartbeatByConnectionId(connectionId);
+            verify(taskService).findAll(query);
+        }
+        @Test
+        @DisplayName("test findHeartbeatByTaskId method normal")
+        void test2(){
+            connectionId = "111";
+            includeFields = "field";
+            Query query = Query.query(Criteria.where("dag.nodes.connectionId").is(connectionId)
+                    .and("syncType").is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
+                    .and("is_deleted").is(false)
+            );
+            query.fields().include(includeFields);
+            doCallRealMethod().when(taskService).findHeartbeatByConnectionId(connectionId,includeFields);
+            taskService.findHeartbeatByConnectionId(connectionId,includeFields);
+            verify(taskService).findAll(query);
+        }
+    }
+    @Nested
+    class FindHeartbeatByTaskIdTest{
+        private String taskId;
+        private String includeFields;
+        @Test
+        @DisplayName("test findHeartbeatByTaskId method when includeFields is null")
+        void test1(){
+            taskId = "111";
+            Query query = Query.query(Criteria.where(ConnHeartbeatUtils.TASK_RELATION_FIELD).is(taskId)
+                    .and("syncType").is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
+                    .and("is_deleted").is(false)
+            );
+            doCallRealMethod().when(taskService).findHeartbeatByTaskId(taskId);
+            taskService.findHeartbeatByTaskId(taskId);
+            verify(taskService).findOne(query);
+        }
+        @Test
+        @DisplayName("test findHeartbeatByTaskId method normal")
+        void test2(){
+            taskId = "111";
+            includeFields = "field";
+            Query query = Query.query(Criteria.where(ConnHeartbeatUtils.TASK_RELATION_FIELD).is(taskId)
+                    .and("syncType").is(TaskDto.SYNC_TYPE_CONN_HEARTBEAT)
+                    .and("is_deleted").is(false)
+            );
+            query.fields().include(includeFields);
+            doCallRealMethod().when(taskService).findHeartbeatByTaskId(taskId,includeFields);
+            taskService.findHeartbeatByTaskId(taskId,includeFields);
+            verify(taskService).findOne(query);
+        }
+    }
+    @Nested
+    class DeleteHeartbeatByConnIdTest{
+        @Test
+        @DisplayName("test deleteHeartbeatByConnId method when heartbeatTasks is null")
+        void test1(){
+            String connId = "111";
+            when(taskService.findHeartbeatByConnectionId(connId, "_id", "status", "is_deleted")).thenReturn(null);
+            doCallRealMethod().when(taskService).deleteHeartbeatByConnId(user,connId);
+            int actual = taskService.deleteHeartbeatByConnId(user, connId);
+            assertEquals(0,actual);
+        }
+        @Test
+        @DisplayName("test deleteHeartbeatByConnId method when status is running")
+        void test2(){
+            String connId = "111";
+            List<TaskDto> heartbeatTasks = new ArrayList<>();
+            TaskDto task = new TaskDto();
+            task.setStatus("running");
+            ObjectId id = mock(ObjectId.class);
+            task.setId(id);
+            heartbeatTasks.add(task);
+            TaskDto task1 = new TaskDto();
+            task1.setStatus("edit");
+            heartbeatTasks.add(task1);
+            TaskDto task2 = new TaskDto();
+            task2.setStatus("stopping");
+            heartbeatTasks.add(task2);
+            when(taskService.findHeartbeatByConnectionId(connId, "_id", "status", "is_deleted")).thenReturn(heartbeatTasks);
+            when(taskService.findByTaskId(any(ObjectId.class),anyString())).thenReturn(task,task1,task2);
+            doCallRealMethod().when(taskService).deleteHeartbeatByConnId(user,connId);
+            int actual = taskService.deleteHeartbeatByConnId(user, connId);
+            assertEquals(3,actual);
+            verify(taskService,new Times(2)).findByTaskId(any(ObjectId.class),anyString());
         }
     }
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java
index a62c994466..ac9ff8dafa 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java
@@ -33,6 +33,7 @@
 import com.tapdata.tm.worker.service.WorkerService;
 import com.tapdata.tm.worker.vo.CalculationEngineVo;
 import org.bson.BsonValue;
+import org.bson.Document;
 import org.bson.types.ObjectId;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -1580,6 +1581,91 @@ public BsonValue getUpsertedId() {
         }
 
     }
+    @Nested
+    class CheckShareCdcStatusTest{
+        TaskRepository taskRepository = mock(TaskRepository.class);
+        TaskServiceImpl taskService = spy(new TaskServiceImpl(taskRepository));
+        @DisplayName("Main process")
+        @Test
+        void test() {
+            TaskDto taskDto = new TaskDto();
+            taskDto.setShareCdcEnable(false);
+            taskDto.setShareCdcStop(true);
+            taskDto.setShareCdcStopMessage("test");
+            doAnswer(invocationOnMock -> {
+                Update set = invocationOnMock.getArgument(1);
+                Document result = (Document) set.getUpdateObject().get("$unset");
+                Assertions.assertTrue(result.containsKey("shareCdcStop"));
+                Assertions.assertTrue(result.containsKey("shareCdcStopMessage"));
+                return null;
+            }).when(taskService).update(any(),any(),any());
+            taskService.checkShareCdcStatus(taskDto,mock(UserDetail.class));
+        }
+
+        @DisplayName("ShareCdcEnable is Null")
+        @Test
+        void test1() {
+            TaskDto taskDto = new TaskDto();
+            taskService.checkShareCdcStatus(taskDto,mock(UserDetail.class));
+            verify(taskService,times(0)).update(any(),any(),any());
+        }
+
+        @DisplayName("ShareCdcEnable is true")
+        @Test
+        void test2() {
+            TaskDto taskDto = new TaskDto();
+            taskDto.setShareCdcEnable(true);
+            taskService.checkShareCdcStatus(taskDto,mock(UserDetail.class));
+            verify(taskService,times(0)).update(any(),any(),any());
+        }
+
+        @DisplayName("ShareCdcStop is null")
+        @Test
+        void test3() {
+            TaskDto taskDto = new TaskDto();
+            taskDto.setShareCdcEnable(false);
+            taskService.checkShareCdcStatus(taskDto,mock(UserDetail.class));
+            verify(taskService,times(0)).update(any(),any(),any());
+        }
+
+        @DisplayName("ShareCdcStopMessage is null")
+        @Test
+        void test4() {
+            TaskDto taskDto = new TaskDto();
+            taskDto.setShareCdcEnable(false);
+            taskDto.setShareCdcStop(true);
+            taskService.checkShareCdcStatus(taskDto,mock(UserDetail.class));
+            verify(taskService,times(0)).update(any(),any(),any());
+        }
+    }
+
+    @Nested
+    class ConfirmByIdTest{
+        TaskRepository taskRepository = mock(TaskRepository.class);
+        TaskServiceImpl taskService = spy(new TaskServiceImpl(taskRepository));
+        @DisplayName("Main process")
+        @Test
+        void test() {
+            TaskDto taskDto = new TaskDto();
+            taskDto.setId(new ObjectId());
+            taskDto.setShareCdcEnable(false);
+            taskDto.setShareCdcStop(true);
+            taskDto.setShareCdcStopMessage("test");
+            UserDetail userDetail = mock(UserDetail.class);
+            doNothing().when(taskService).checkTaskInspectFlag(any());
+            doNothing().when(taskService).checkDagAgentConflict(taskDto,userDetail,true);
+            doNothing().when(taskService).checkDDLConflict(any());
+            doAnswer(invocationOnMock -> {
+                Update set = invocationOnMock.getArgument(1);
+                Document result = (Document) set.getUpdateObject().get("$unset");
+                Assertions.assertTrue(result.containsKey("shareCdcStop"));
+                Assertions.assertTrue(result.containsKey("shareCdcStopMessage"));
+                return null;
+            }).when(taskService).update(any(),any(),any());
+            doReturn(taskDto).when(taskService).confirmById(taskDto,userDetail,true,false);
+            taskService.confirmById(taskDto,userDetail,true);
+        }
+    }
 
 
 }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TransformSchemaServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TransformSchemaServiceTest.java
index 42b72d6c67..48fedac5a0 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/TransformSchemaServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TransformSchemaServiceTest.java
@@ -1,9 +1,12 @@
 package com.tapdata.tm.task.service;
 
+import com.mongodb.client.result.UpdateResult;
 import com.tapdata.tm.agent.service.AgentGroupService;
 import com.tapdata.tm.commons.dag.AccessNodeTypeEnum;
 import com.tapdata.tm.commons.dag.DAG;
 import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.NodeEnum;
+import com.tapdata.tm.commons.dag.nodes.CacheNode;
 import com.tapdata.tm.commons.dag.nodes.DataParentNode;
 import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
 import com.tapdata.tm.commons.dag.nodes.TableNode;
@@ -31,6 +34,7 @@
 import org.mockito.internal.verification.Times;
 import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
@@ -52,6 +56,7 @@ class TransformSchemaServiceTest {
     AgentGroupService agentGroupService;
     WorkerService workerService;
     UserDetail user;
+    TaskService taskService;
 
     @BeforeEach
     void buildTransformSchemaService(){
@@ -70,6 +75,8 @@ void buildTransformSchemaService(){
         ReflectionTestUtils.setField(transformSchemaService,"agentGroupService",agentGroupService);
         workerService = mock(WorkerService.class);
         ReflectionTestUtils.setField(transformSchemaService,"workerService",workerService);
+        taskService = mock(TaskService.class);
+        ReflectionTestUtils.setField(transformSchemaService,"taskService",taskService);
         user = mock(UserDetail.class);
     }
 
@@ -302,4 +309,109 @@ void testWsMessageDtoJsonIsNull() {
                     1);
         }
     }
+
+    @Nested
+    class TransformSchemaTest {
+        TaskDto taskDto;
+        DAG dag;
+        List<Node> targets;
+        TableNode target;
+        CacheNode cacheNode;
+        ObjectId taskId;
+        @BeforeEach
+        void init() {
+            taskId = mock(ObjectId.class);
+            target = mock(TableNode.class);
+            when(target.getType()).thenReturn(NodeEnum.database.name());
+            when(target.getNeedDynamicTableName()).thenReturn(true);
+            targets = new ArrayList<>();
+            targets.add(target);
+            cacheNode = mock(CacheNode.class);
+            when(cacheNode.getType()).thenReturn(NodeEnum.mem_cache.name());
+            targets.add(cacheNode);
+
+            taskDto = mock(TaskDto.class);
+            dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            when(dag.getTargets()).thenReturn(targets);
+            when(taskDto.getId()).thenReturn(taskId);
+            when(taskDto.getSyncType()).thenReturn(TaskDto.SYNC_TYPE_SYNC);
+        }
+
+        @Nested
+        class TransformSchemaBeforeDynamicTableNameTest {
+            @BeforeEach
+            void init() {
+                doNothing().when(transformSchemaService).transformSchemaAndUpdateTask(taskDto, user);
+                doCallRealMethod().when(transformSchemaService).transformSchemaBeforeDynamicTableName(taskDto, user);
+            }
+
+            @Test
+            void testNormal() {
+                transformSchemaService.transformSchemaBeforeDynamicTableName(taskDto, user);
+                verify(taskDto).getSyncType();
+                verify(taskDto).getDag();
+                verify(dag).getTargets();
+                verify(target).getNeedDynamicTableName();
+                verify(transformSchemaService).transformSchemaAndUpdateTask(taskDto, user);
+            }
+            @Test
+            void testTaskNotSync() {
+                when(taskDto.getSyncType()).thenReturn(TaskDto.SYNC_TYPE_MIGRATE);
+                transformSchemaService.transformSchemaBeforeDynamicTableName(taskDto, user);
+                verify(taskDto).getSyncType();
+                verify(taskDto, times(0)).getDag();
+                verify(dag, times(0)).getTargets();
+                verify(target, times(0)).getNeedDynamicTableName();
+                verify(transformSchemaService, times(0)).transformSchemaAndUpdateTask(taskDto, user);
+            }
+            @Test
+            void testNotNeedDynamicTableName() {
+                when(target.getNeedDynamicTableName()).thenReturn(false);
+                transformSchemaService.transformSchemaBeforeDynamicTableName(taskDto, user);
+                verify(taskDto).getSyncType();
+                verify(taskDto).getDag();
+                verify(dag).getTargets();
+                verify(target).getNeedDynamicTableName();
+                verify(transformSchemaService, times(0)).transformSchemaAndUpdateTask(taskDto, user);
+            }
+        }
+
+        @Nested
+        class TransformSchemaAndUpdateTaskTest {
+
+            @BeforeEach
+            void init() {
+                when(dag.getNodes()).thenReturn(targets);
+                doNothing().when(transformSchemaService).transformSchema(dag, user, taskId);
+                doCallRealMethod().when(transformSchemaService).transformSchemaAndUpdateTask(taskDto, user);
+                when(taskService.updateById(any(ObjectId.class), any(Update.class), any(UserDetail.class))).thenReturn(mock(UpdateResult.class));
+            }
+
+            @Test
+            void testNormal() {
+                transformSchemaService.transformSchemaAndUpdateTask(taskDto, user);
+                verify(taskDto).getDag();
+                verify(taskDto).getId();
+                verify(transformSchemaService).transformSchema(dag, user, taskId);
+                verify(dag).getNodes();
+                verify(target).getType();
+                verify(cacheNode).getType();
+                verify(taskService, times(0)).updateById(any(ObjectId.class), any(Update.class), any(UserDetail.class));
+            }
+
+            @Test
+            void testContainsJs() {
+                when(cacheNode.getType()).thenReturn(NodeEnum.join_processor.name());
+                transformSchemaService.transformSchemaAndUpdateTask(taskDto, user);
+                verify(taskDto).getDag();
+                verify(taskDto).getId();
+                verify(transformSchemaService).transformSchema(dag, user, taskId);
+                verify(dag).getNodes();
+                verify(target).getType();
+                verify(cacheNode).getType();
+                verify(taskService).updateById(any(ObjectId.class), any(Update.class), any(UserDetail.class));
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/webhook/controller/WebHookControllerTest.java b/manager/tm/src/test/java/com/tapdata/tm/webhook/controller/WebHookControllerTest.java
new file mode 100644
index 0000000000..974abbd96c
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/webhook/controller/WebHookControllerTest.java
@@ -0,0 +1,246 @@
+package com.tapdata.tm.webhook.controller;
+
+
+import com.tapdata.tm.base.dto.Filter;
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.dto.ResponseMessage;
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.utils.WebUtils;
+import com.tapdata.tm.webhook.dto.HookOneHistoryDto;
+import com.tapdata.tm.webhook.dto.WebHookInfoDto;
+import com.tapdata.tm.webhook.entity.HookOneHistory;
+import com.tapdata.tm.webhook.server.WebHookAdapterService;
+import com.tapdata.tm.webhook.server.WebHookHttpUtilService;
+import com.tapdata.tm.webhook.server.WebHookService;
+import com.tapdata.tm.webhook.vo.WebHookInfoVo;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Locale;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class WebHookControllerTest {
+    WebHookController webHookController;
+    WebHookService<WebHookInfoVo> webHookService;
+    WebHookAdapterService webHookAdapter;
+    WebHookHttpUtilService webHookHttpUtil;
+    UserDetail user;
+
+    @BeforeEach
+    void init() {
+        user = mock(UserDetail.class);
+        webHookController = mock(WebHookController.class);
+        webHookService = mock(WebHookService.class);
+        webHookHttpUtil = mock(WebHookHttpUtilService.class);
+        webHookAdapter = mock(WebHookAdapterService.class);
+
+        doCallRealMethod().when(webHookController).setWebHookService(webHookService);
+        webHookController.setWebHookService(webHookService);
+
+        when(webHookController.getLoginUser()).thenReturn(user);
+    }
+
+    @Test
+    void testCurrentUserWebHookInfoList() {
+        Page<WebHookInfoVo> page = mock(Page.class);
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        Locale locale = mock(Locale.class);
+        Filter filter = mock(Filter.class);
+        when(webHookController.parseFilter("{}")).thenReturn(filter);
+        when(webHookController.success(page)).thenReturn(mock(ResponseMessage.class));
+        when(webHookService.list(filter, user, locale)).thenReturn(page);
+        when(webHookController.currentUserWebHookInfoList("{}", request)).thenCallRealMethod();
+        try (MockedStatic<WebUtils> wu = mockStatic(WebUtils.class)) {
+            wu.when(() -> WebUtils.getLocale(request)).thenReturn(locale);
+            Assertions.assertNotNull(webHookController.currentUserWebHookInfoList("{}", request));
+            verify(webHookController).parseFilter("{}");
+            verify(webHookController).success(page);
+            verify(webHookService).list(filter, user, locale);
+        }
+    }
+
+    @Test
+    void testCreate() {
+        WebHookInfoVo vo = new WebHookInfoVo();
+        WebHookInfoDto dto = new WebHookInfoDto();
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        Locale locale = mock(Locale.class);
+        when(webHookController.success(vo)).thenReturn(mock(ResponseMessage.class));
+        when(webHookService.create(dto, user)).thenReturn(vo);
+        when(webHookController.create(dto, request)).thenCallRealMethod();
+        try (MockedStatic<WebUtils> wu = mockStatic(WebUtils.class)) {
+            wu.when(() -> WebUtils.getLocale(request)).thenReturn(locale);
+            Assertions.assertNotNull(webHookController.create(dto, request));
+            verify(webHookController).success(vo);
+            verify(webHookService).create(dto, user);
+            Assertions.assertEquals(locale, dto.getLocale());
+        }
+    }
+
+    @Test
+    void testFindWebHookByHookId() {
+        WebHookInfoVo vo = new WebHookInfoVo();
+        when(webHookController.success(vo)).thenReturn(mock(ResponseMessage.class));
+        when(webHookService.findWebHookByHookId("id", user)).thenReturn(vo);
+        when(webHookController.findWebHookByHookId("id")).thenCallRealMethod();
+        ResponseMessage<WebHookInfoVo> hook = webHookController.findWebHookByHookId("id");
+        Assertions.assertNotNull(hook);
+        verify(webHookController).success(vo);
+        verify(webHookService).findWebHookByHookId("id", user);
+        verify(webHookController).findWebHookByHookId("id");
+    }
+
+    @Test
+    void testUpdate() {
+        WebHookInfoVo vo = new WebHookInfoVo();
+        WebHookInfoDto dto = new WebHookInfoDto();
+        when(webHookController.success(vo)).thenReturn(mock(ResponseMessage.class));
+        when(webHookService.update(dto, user)).thenReturn(vo);
+        when(webHookController.update(dto)).thenCallRealMethod();
+        ResponseMessage<WebHookInfoVo> hook = webHookController.update(dto);
+        Assertions.assertNotNull(hook);
+        verify(webHookController).success(vo);
+        verify(webHookService).update(dto, user);
+        verify(webHookController).update(dto);
+    }
+
+    @Nested
+    class CloseOneWebHookByHookIdTest {
+        @Test
+        void testCloseOneWebHookByHookId() {
+            WebHookInfoVo vo = new WebHookInfoVo();
+            when(webHookController.success(vo)).thenReturn(mock(ResponseMessage.class));
+            List mock = mock(List.class);
+            when(mock.isEmpty()).thenReturn(false);
+            when(mock.get(0)).thenReturn(vo);
+            when(webHookService.close(any(String[].class), any(UserDetail.class))).thenReturn(mock);
+            when(webHookController.closeOneWebHookByHookId("id")).thenCallRealMethod();
+            ResponseMessage<WebHookInfoVo> hook = webHookController.closeOneWebHookByHookId("id");
+            Assertions.assertNotNull(hook);
+            verify(webHookController).success(vo);
+            verify(webHookService).close(any(String[].class), any(UserDetail.class));
+        }
+        @Test
+        void testEmpty() {
+            when(webHookController.success(null)).thenReturn(mock(ResponseMessage.class));
+            List mock = mock(List.class);
+            when(mock.isEmpty()).thenReturn(true);
+            when(webHookService.close(any(String[].class), any(UserDetail.class))).thenReturn(mock);
+            when(webHookController.closeOneWebHookByHookId("id")).thenCallRealMethod();
+            ResponseMessage<WebHookInfoVo> hook = webHookController.closeOneWebHookByHookId("id");
+            Assertions.assertNotNull(hook);
+            Assertions.assertNull(hook.getData());
+            verify(webHookController).success(null);
+            verify(webHookService).close(any(String[].class), any(UserDetail.class));
+        }
+    }
+    @Nested
+    class ReOpenIdTest {
+        @Test
+        void testCloseOneWebHookByHookId() {
+            WebHookInfoVo vo = new WebHookInfoVo();
+            when(webHookController.success(vo)).thenReturn(mock(ResponseMessage.class));
+            List mock = mock(List.class);
+            when(mock.isEmpty()).thenReturn(false);
+            when(mock.get(0)).thenReturn(vo);
+            when(webHookService.reOpen(any(String[].class), any(UserDetail.class))).thenReturn(mock);
+            when(webHookController.reOpenOne("id")).thenCallRealMethod();
+            ResponseMessage<WebHookInfoVo> hook = webHookController.reOpenOne("id");
+            Assertions.assertNotNull(hook);
+            verify(webHookController).success(vo);
+            verify(webHookService).reOpen(any(String[].class), any(UserDetail.class));
+        }
+        @Test
+        void testEmpty() {
+            when(webHookController.failed("webhook.reOpen.failed")).thenReturn(mock(ResponseMessage.class));
+            List mock = mock(List.class);
+            when(mock.isEmpty()).thenReturn(true);
+            when(webHookService.reOpen(any(String[].class), any(UserDetail.class))).thenReturn(mock);
+            when(webHookController.reOpenOne("id")).thenCallRealMethod();
+            ResponseMessage<WebHookInfoVo> hook = webHookController.reOpenOne("id");
+            Assertions.assertNotNull(hook);
+            verify(webHookController).failed("webhook.reOpen.failed");
+            verify(webHookService).reOpen(any(String[].class), any(UserDetail.class));
+        }
+    }
+
+    @Test
+    void testCloseWebHookByHookIds() {
+        List<WebHookInfoVo> mock = mock(List.class);
+        when(webHookController.success(mock)).thenReturn(mock(ResponseMessage.class));
+        String[] ids = new String[]{"id"};
+        when(webHookService.close(ids, user)).thenReturn(mock);
+        when(webHookController.closeWebHookByHookIds(ids)).thenCallRealMethod();
+        ResponseMessage<List<WebHookInfoVo>> hook = webHookController.closeWebHookByHookIds(ids);
+        Assertions.assertNotNull(hook);
+        verify(webHookController).success(mock);
+        verify(webHookService).close(ids, user);
+    }
+    @Test
+    void testReOpenAll() {
+        List<WebHookInfoVo> mock = mock(List.class);
+        when(webHookController.success(mock)).thenReturn(mock(ResponseMessage.class));
+        String[] ids = new String[]{"id"};
+        when(webHookService.reOpen(ids, user)).thenReturn(mock);
+        when(webHookController.reOpenAll(ids)).thenCallRealMethod();
+        ResponseMessage<List<WebHookInfoVo>> hook = webHookController.reOpenAll(ids);
+        Assertions.assertNotNull(hook);
+        verify(webHookController).success(mock);
+        verify(webHookService).reOpen(ids, user);
+    }
+
+    @Test
+    void testDeleteOneWebHookByHookId() {
+        when(webHookController.success()).thenReturn(mock(ResponseMessage.class));
+        doNothing().when(webHookService).delete(any(String[].class), any(UserDetail.class));
+        when(webHookController.deleteOneWebHookByHookId("id")).thenCallRealMethod();
+        ResponseMessage<Void> hook = webHookController.deleteOneWebHookByHookId("id");
+        Assertions.assertNotNull(hook);
+        verify(webHookController).success();
+        verify(webHookService).delete(any(String[].class), any(UserDetail.class));
+    }
+
+    @Test
+    void testDeleteWebHookByHookIds() {
+        when(webHookController.success()).thenReturn(mock(ResponseMessage.class));
+        String[] ids = new String[]{"id"};
+        doNothing().when(webHookService).delete(ids, user);
+        when(webHookController.deleteWebHookByHookIds(ids)).thenCallRealMethod();
+        webHookController.deleteWebHookByHookIds(ids);
+        verify(webHookController).success();
+        verify(webHookService).delete(ids, user);
+    }
+
+    @Test
+    void testPing() {
+        try(MockedStatic<MessageUtil> mu = mockStatic(MessageUtil.class)) {
+            mu.when(() -> MessageUtil.getMessage("webhook.ping.succeed")).thenReturn("ducceed");
+            HookOneHistoryDto vo = new HookOneHistoryDto();
+            WebHookInfoDto dto = new WebHookInfoDto();
+            when(webHookController.success(vo)).thenReturn(mock(ResponseMessage.class));
+            when(webHookService.ping(dto, user)).thenReturn(vo);
+            when(webHookController.ping(dto)).thenCallRealMethod();
+            ResponseMessage<HookOneHistoryDto> hook = webHookController.ping(dto);
+            Assertions.assertNotNull(hook);
+            verify(webHookController).success(vo);
+            verify(webHookService).ping(dto, user);
+            verify(webHookController).ping(dto);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/webhook/controller/WebHookHistoryControllerTest.java b/manager/tm/src/test/java/com/tapdata/tm/webhook/controller/WebHookHistoryControllerTest.java
new file mode 100644
index 0000000000..91964b9d13
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/webhook/controller/WebHookHistoryControllerTest.java
@@ -0,0 +1,147 @@
+package com.tapdata.tm.webhook.controller;
+
+
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.dto.ResponseMessage;
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.utils.WebUtils;
+import com.tapdata.tm.webhook.dto.HookOneHistoryDto;
+import com.tapdata.tm.webhook.params.HistoryPageParam;
+import com.tapdata.tm.webhook.server.WebHookHistoryService;
+import com.tapdata.tm.webhook.vo.WebHookHistoryInfoVo;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Locale;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class WebHookHistoryControllerTest {
+    WebHookHistoryService<WebHookHistoryInfoVo> webHookHistoryService;
+    WebHookHistoryController controller;
+    UserDetail user;
+
+    @BeforeEach
+    void init() {
+        user = mock(UserDetail.class);
+        controller = mock(WebHookHistoryController.class);
+        webHookHistoryService = mock(WebHookHistoryService.class);
+        ReflectionTestUtils.setField(controller, "webHookHistoryService", webHookHistoryService);
+        when(controller.getLoginUser()).thenReturn(user);
+    }
+
+    @Nested
+    class CurrentUserWebHookInfoListTest {
+        @Test
+        void testCurrentUserWebHookInfoListNormal() {
+            Locale locale = mock(Locale.class);
+            HttpServletRequest request = mock(HttpServletRequest.class);
+            Page<WebHookHistoryInfoVo> page = mock(Page.class);
+            when(controller.success(page)).thenReturn(mock(ResponseMessage.class));
+            when(webHookHistoryService.list(any(HistoryPageParam.class), any(UserDetail.class), any(Locale.class))).thenReturn(page);
+            when(controller.currentUserWebHookInfoList("id", 0, 1, request)).thenCallRealMethod();
+            try (MockedStatic<WebUtils> wu = mockStatic(WebUtils.class)) {
+                wu.when(() -> WebUtils.getLocale(request)).thenReturn(locale);
+                Assertions.assertNotNull(controller.currentUserWebHookInfoList("id", 0, 1, request));
+                verify(controller).getLoginUser();
+                verify(controller).success(page);
+                verify(webHookHistoryService).list(any(HistoryPageParam.class), any(UserDetail.class), any(Locale.class));
+            }
+        }
+        @Test
+        void testCurrentUserWebHookInfoListIdInvalid() {
+            HistoryPageParam pageParam = new HistoryPageParam();
+            Locale locale = mock(Locale.class);
+            HttpServletRequest request = mock(HttpServletRequest.class);
+            Page<WebHookHistoryInfoVo> page = mock(Page.class);
+            when(controller.success(page)).thenReturn(mock(ResponseMessage.class));
+            when(webHookHistoryService.list(pageParam, user, locale)).thenReturn(page);
+            when(controller.currentUserWebHookInfoList(null, 0, 1, request)).thenCallRealMethod();
+            try (MockedStatic<WebUtils> wu = mockStatic(WebUtils.class)) {
+                wu.when(() -> WebUtils.getLocale(request)).thenReturn(locale);
+                Assertions.assertThrows(BizException.class, () -> {
+                    try {
+                        controller.currentUserWebHookInfoList(null, 0, 1, request);
+                    } catch (BizException e) {
+                        Assertions.assertEquals("webhook.history.hook.id.error", e.getErrorCode());
+                        throw e;
+                    }
+                });
+                verify(controller, times(0)).getLoginUser();
+                verify(controller, times(0)).success(page);
+                verify(webHookHistoryService, times(0)).list(pageParam, user, locale);
+            }
+        }
+        @Test
+        void testCurrentUserWebHookInfoListPageFromInvalid() {
+            HistoryPageParam pageParam = new HistoryPageParam();
+            Locale locale = mock(Locale.class);
+            HttpServletRequest request = mock(HttpServletRequest.class);
+            Page<WebHookHistoryInfoVo> page = mock(Page.class);
+            when(controller.success(page)).thenReturn(mock(ResponseMessage.class));
+            when(webHookHistoryService.list(pageParam, user, locale)).thenReturn(page);
+            when(controller.currentUserWebHookInfoList("id", -1, 1, request)).thenCallRealMethod();
+            try (MockedStatic<WebUtils> wu = mockStatic(WebUtils.class)) {
+                wu.when(() -> WebUtils.getLocale(request)).thenReturn(locale);
+                Assertions.assertThrows(BizException.class, () -> {
+                    try {
+                        controller.currentUserWebHookInfoList("id", -1, 1, request);
+                    } catch (BizException e) {
+                        Assertions.assertEquals("webhook.history.page.from.error", e.getErrorCode());
+                        throw e;
+                    }
+                });
+                verify(controller, times(0)).getLoginUser();
+                verify(controller, times(0)).success(page);
+                verify(webHookHistoryService, times(0)).list(pageParam, user, locale);
+            }
+        }
+        @Test
+        void testCurrentUserWebHookInfoListPageSizeInvalid() {
+            HistoryPageParam pageParam = new HistoryPageParam();
+            Locale locale = mock(Locale.class);
+            HttpServletRequest request = mock(HttpServletRequest.class);
+            Page<WebHookHistoryInfoVo> page = mock(Page.class);
+            when(controller.success(page)).thenReturn(mock(ResponseMessage.class));
+            when(webHookHistoryService.list(pageParam, user, locale)).thenReturn(page);
+            when(controller.currentUserWebHookInfoList("id", 0, -1, request)).thenCallRealMethod();
+            try (MockedStatic<WebUtils> wu = mockStatic(WebUtils.class)) {
+                wu.when(() -> WebUtils.getLocale(request)).thenReturn(locale);
+                Assertions.assertThrows(BizException.class, () -> {
+                    try {
+                        controller.currentUserWebHookInfoList("id", 0, -1, request);
+                    } catch (BizException e) {
+                        Assertions.assertEquals("webhook.history.page.size.error", e.getErrorCode());
+                        throw e;
+                    }
+                });
+                verify(controller, times(0)).getLoginUser();
+                verify(controller, times(0)).success(page);
+                verify(webHookHistoryService, times(0)).list(pageParam, user, locale);
+            }
+        }
+    }
+
+    @Test
+    void testReSend() {
+        WebHookHistoryInfoVo vo = new WebHookHistoryInfoVo();
+        HookOneHistoryDto historyDto = new HookOneHistoryDto();
+        when(webHookHistoryService.reSend(historyDto, user)).thenReturn(vo);
+        when(controller.success(vo)).thenReturn(mock(ResponseMessage.class));
+        when(controller.reSend(historyDto)).thenCallRealMethod();
+        Assertions.assertNotNull(controller.reSend(historyDto));
+        verify(webHookHistoryService).reSend(historyDto, user);
+        verify(controller).success(vo);
+    }
+}
\ No newline at end of file