content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.alibaba.nacos.config.server.service.repository.extrnal;
  
  import com.alibaba.nacos.api.exception.NacosException;
  import com.alibaba.nacos.common.utils.MD5Utils;
  import com.alibaba.nacos.common.utils.StringUtils;
  import com.alibaba.nacos.config.server.constant.Constants;
  import com.alibaba.nacos.config.server.model.ConfigAdvanceInfo;
  import com.alibaba.nacos.config.server.model.ConfigAllInfo;
  import com.alibaba.nacos.config.server.model.ConfigInfo;
  import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper;
  import com.alibaba.nacos.config.server.model.ConfigInfoWrapper;
  import com.alibaba.nacos.config.server.model.ConfigOperateResult;
  import com.alibaba.nacos.api.config.model.SameConfigPolicy;
  import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService;
  import com.alibaba.nacos.config.server.service.sql.ExternalStorageUtils;
  import com.alibaba.nacos.config.server.utils.ConfigExtInfoUtil;
  import com.alibaba.nacos.config.server.utils.TestCaseUtils;
  import com.alibaba.nacos.persistence.datasource.DataSourceService;
  import com.alibaba.nacos.persistence.datasource.DynamicDataSource;
  import com.alibaba.nacos.api.model.Page;
  import com.alibaba.nacos.plugin.datasource.MapperManager;
  import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
  import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoMapper;
  import com.alibaba.nacos.sys.env.EnvUtil;
  import org.junit.jupiter.api.AfterEach;
  import org.junit.jupiter.api.BeforeEach;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.api.extension.ExtendWith;
  import org.mockito.Mock;
  import org.mockito.MockedStatic;
  import org.mockito.Mockito;
  import org.springframework.dao.CannotAcquireLockException;
  import org.springframework.dao.EmptyResultDataAccessException;
  import org.springframework.dao.IncorrectResultSizeDataAccessException;
  import org.springframework.jdbc.CannotGetJdbcConnectionException;
  import org.springframework.jdbc.core.JdbcTemplate;
  import org.springframework.jdbc.core.PreparedStatementCreator;
  import org.springframework.jdbc.support.GeneratedKeyHolder;
  import org.springframework.jdbc.support.KeyHolder;
  import org.springframework.test.context.junit.jupiter.SpringExtension;
  import org.springframework.test.util.ReflectionTestUtils;
  import org.springframework.transaction.support.TransactionTemplate;
  
  import java.sql.Connection;
  import java.sql.PreparedStatement;
  import java.sql.SQLException;
  import java.sql.Timestamp;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_ADVANCE_INFO_ROW_MAPPER;
  import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_ALL_INFO_ROW_MAPPER;
  import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_ROW_MAPPER;
  import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER;
  import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_WRAPPER_ROW_MAPPER;
  import static org.junit.jupiter.api.Assertions.assertEquals;
  import static org.junit.jupiter.api.Assertions.assertFalse;
  import static org.junit.jupiter.api.Assertions.assertNull;
  import static org.junit.jupiter.api.Assertions.assertTrue;
  import static org.mockito.ArgumentMatchers.any;
  import static org.mockito.ArgumentMatchers.anyInt;
  import static org.mockito.ArgumentMatchers.anyString;
  import static org.mockito.ArgumentMatchers.eq;
  import static org.mockito.Mockito.times;
  import static org.mockito.Mockito.when;
  
  @ExtendWith(SpringExtension.class)
  class ExternalConfigInfoPersistServiceImplTest {
      
      MockedStatic<EnvUtil> envUtilMockedStatic;
      
      MockedStatic<ExternalStorageUtils> externalStorageUtilsMockedStatic;
      
      MockedStatic<DynamicDataSource> dynamicDataSourceMockedStatic;
      
      @Mock
      DynamicDataSource dynamicDataSource;
      
      private ExternalConfigInfoPersistServiceImpl externalConfigInfoPersistService;
      
      @Mock
      private HistoryConfigInfoPersistService historyConfigInfoPersistService;
      
      @Mock
      private DataSourceService dataSourceService;
      
      @Mock
      private JdbcTemplate jdbcTemplate;
      
      private TransactionTemplate transactionTemplate = TestCaseUtils.createMockTransactionTemplate();
      
      @BeforeEach
      void before() {
          dynamicDataSourceMockedStatic = Mockito.mockStatic(DynamicDataSource.class);
          envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class);
          externalStorageUtilsMockedStatic = Mockito.mockStatic(ExternalStorageUtils.class);
          when(DynamicDataSource.getInstance()).thenReturn(dynamicDataSource);
          when(dynamicDataSource.getDataSource()).thenReturn(dataSourceService);
          when(dataSourceService.getTransactionTemplate()).thenReturn(transactionTemplate);
          when(dataSourceService.getJdbcTemplate()).thenReturn(jdbcTemplate);
          when(dataSourceService.getDataSourceType()).thenReturn("mysql");
          /*when(EnvUtil.getProperty(anyString(), eq(Boolean.class),
                  eq(false))).thenReturn(false);*/
          envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false)))
                  .thenReturn(false);
          externalConfigInfoPersistService = new ExternalConfigInfoPersistServiceImpl(historyConfigInfoPersistService);
      }
      
      @AfterEach
      void after() {
          dynamicDataSourceMockedStatic.close();
          envUtilMockedStatic.close();
          externalStorageUtilsMockedStatic.close();
      }
      
      @Test
      void testInsertOrUpdateOfInsertConfigSuccess() {
          
          String dataId = "dataId";
          String group = "group";
          String tenant = "tenant";
          String appName = "appNameNew";
          String content = "content132456";
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          configAdvanceInfo.put("config_tags", "tag1,tag2");
          ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content);
          long insertConfigIndoId = 12345678765L;
          GeneratedKeyHolder generatedKeyHolder = TestCaseUtils.createGeneratedKeyHolder(insertConfigIndoId);
          externalStorageUtilsMockedStatic.when(ExternalStorageUtils::createKeyHolder).thenReturn(generatedKeyHolder);
          //mock get config state
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null, new ConfigInfoStateWrapper());
          //mock insert config info
          Mockito.when(jdbcTemplate.update(any(PreparedStatementCreator.class), eq(generatedKeyHolder))).thenReturn(1);
          Mockito.when(jdbcTemplate.update(
                          eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                          eq(insertConfigIndoId), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)))
                  .thenReturn(1);
          Mockito.when(jdbcTemplate.update(
                          eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                          eq(insertConfigIndoId), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)))
                  .thenReturn(1);
          String srcIp = "srcIp";
          String srcUser = "srcUser";
          //mock insert config info
          Mockito.doNothing().when(historyConfigInfoPersistService)
                  .insertConfigHistoryAtomic(eq(0), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I"),
                          eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser)));
          
          externalConfigInfoPersistService.insertOrUpdate(srcIp, srcUser, configInfo, configAdvanceInfo);
          //expect insert config info
          Mockito.verify(jdbcTemplate, times(1)).update(any(PreparedStatementCreator.class), eq(generatedKeyHolder));
          //expect insert config tags
          Mockito.verify(jdbcTemplate, times(1)).update(eq(
                          externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                  eq(insertConfigIndoId), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant));
          Mockito.verify(jdbcTemplate, times(1)).update(eq(
                          externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                  eq(insertConfigIndoId), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant));
          
          //expect insert history info
          Mockito.verify(historyConfigInfoPersistService, times(1))
                  .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class),
                          eq("I"), eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser)));
          
      }
      
      @Test
      void testInsertOrUpdateCasOfInsertConfigSuccess() {
          
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          configAdvanceInfo.put("config_tags", "tag1,tag2");
          String dataId = "dataId";
          String group = "group";
          String tenant = "tenant";
          String appName = "appName";
          String content = "content132456";
          ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content);
          long insertConfigIndoId = 12345678765L;
          GeneratedKeyHolder generatedKeyHolder = TestCaseUtils.createGeneratedKeyHolder(insertConfigIndoId);
          externalStorageUtilsMockedStatic.when(ExternalStorageUtils::createKeyHolder).thenReturn(generatedKeyHolder);
          //mock get config state
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null, new ConfigInfoStateWrapper());
          //mock insert config info
          Mockito.when(jdbcTemplate.update(any(PreparedStatementCreator.class), eq(generatedKeyHolder))).thenReturn(1);
          Mockito.when(jdbcTemplate.update(
                          eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                          eq(insertConfigIndoId), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)))
                  .thenReturn(1);
          Mockito.when(jdbcTemplate.update(
                          eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                          eq(insertConfigIndoId), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)))
                  .thenReturn(1);
          String srcIp = "srcIp";
          String srcUser = "srcUser";
          //mock insert config info
          Mockito.doNothing().when(historyConfigInfoPersistService)
                  .insertConfigHistoryAtomic(eq(0), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I"),
                          eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser)));
          
          externalConfigInfoPersistService.insertOrUpdateCas(srcIp, srcUser, configInfo, configAdvanceInfo);
          //expect insert config info
          Mockito.verify(jdbcTemplate, times(1)).update(any(PreparedStatementCreator.class), eq(generatedKeyHolder));
          //expect insert config tags
          Mockito.verify(jdbcTemplate, times(1)).update(eq(
                          externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                  eq(insertConfigIndoId), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant));
          Mockito.verify(jdbcTemplate, times(1)).update(eq(
                          externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                  eq(insertConfigIndoId), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant));
          
          //expect insert history info
          Mockito.verify(historyConfigInfoPersistService, times(1))
                  .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class),
                          eq("I"), eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser)));
          
      }
      
      @Test
      void testInsertOrUpdateOfException() {
          String dataId = "dataId";
          String group = "group";
          String tenant = "tenant";
          //mock get config state
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null);
          //mock insert config throw exception
          long insertConfigIndoId = 12345678765L;
          GeneratedKeyHolder generatedKeyHolder = TestCaseUtils.createGeneratedKeyHolder(insertConfigIndoId);
          externalStorageUtilsMockedStatic.when(ExternalStorageUtils::createKeyHolder).thenReturn(generatedKeyHolder);
          Mockito.when(jdbcTemplate.update(any(PreparedStatementCreator.class), any(KeyHolder.class)))
                  .thenThrow(new CannotGetJdbcConnectionException("mock fail"));
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          configAdvanceInfo.put("config_tags", "tag1,tag2");
          ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, null, "content");
          try {
              externalConfigInfoPersistService.insertOrUpdate("srcIp", "srcUser", configInfo, configAdvanceInfo);
              assertTrue(false);
          } catch (Exception e) {
              assertEquals("mock fail", e.getMessage());
          }
          
      }
      
      @Test
      void testInsertOrUpdateOfUpdateConfigSuccess() {
          
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          configAdvanceInfo.put("config_tags", "tag1,tag2");
          configAdvanceInfo.put("desc", "desc11");
          configAdvanceInfo.put("use", "use2233");
          configAdvanceInfo.put("effect", "effect222");
          configAdvanceInfo.put("type", "type3");
          configAdvanceInfo.put("schema", "schema");
          
          String dataId = "dataId";
          String group = "group";
          String tenant = "tenant";
          String content = "content132456";
          
          ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, null, content);
          String encryptedDataKey = "key34567";
          configInfo.setEncryptedDataKey(encryptedDataKey);
          //mock get config state,first and second is not null
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                          eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER)))
                  .thenReturn(new ConfigInfoStateWrapper(), new ConfigInfoStateWrapper());
          
          ConfigAllInfo configAllInfo = new ConfigAllInfo();
          configAllInfo.setDataId(dataId);
          configAllInfo.setGroup(group);
          configAllInfo.setTenant(tenant);
          configAllInfo.setAppName("old_app");
          configAllInfo.setMd5("old_md5");
          configAllInfo.setId(12345678765L);
          //mock get all config info
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(configAllInfo);
          
          String srcIp = "srcIp";
          String srcUser = "srcUser";
          //mock update config info
          Mockito.when(jdbcTemplate.update(
                  eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                  TableConstant.CONFIG_INFO)
                          .update(Arrays.asList("content", "md5", "src_ip", "src_user", "gmt_modified@NOW()", "app_name",
                                          "c_desc", "c_use", "effect", "type", "c_schema", "encrypted_data_key"),
                                  Arrays.asList("data_id", "group_id", "tenant_id"))), eq(configInfo.getContent()),
                  eq(configInfo.getMd5()), eq(srcIp), eq(srcUser), eq(configAllInfo.getAppName()),
                  eq(configAdvanceInfo.get("desc")), eq(configAdvanceInfo.get("use")),
                  eq(configAdvanceInfo.get("effect")), eq(configAdvanceInfo.get("type")),
                  eq(configAdvanceInfo.get("schema")), eq(encryptedDataKey), eq(configInfo.getDataId()),
                  eq(configInfo.getGroup()), eq(tenant))).thenReturn(1);
          
          //mock insert config tags.
          Mockito.when(jdbcTemplate.update(
                  eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                  TableConstant.CONFIG_TAGS_RELATION)
                          .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                  eq(12345678765L), anyString(), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))).thenReturn(1);
          
          //mock insert his config info
          Mockito.doNothing().when(historyConfigInfoPersistService)
                  .insertConfigHistoryAtomic(eq(configAllInfo.getId()), eq(configInfo), eq(srcIp), eq(srcUser),
                          any(Timestamp.class), eq("I"), eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo)));
          
          externalConfigInfoPersistService.insertOrUpdate(srcIp, srcUser, configInfo, configAdvanceInfo);
          
          //expect update config tags
          Mockito.verify(jdbcTemplate, times(1)).update(eq(
                          externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                  eq(configAllInfo.getId()), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant));
          Mockito.verify(jdbcTemplate, times(1)).update(eq(
                          externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                  eq(configAllInfo.getId()), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant));
          
          //expect insert history info
          Mockito.verify(historyConfigInfoPersistService, times(1))
                  .insertConfigHistoryAtomic(eq(configAllInfo.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser),
                          any(Timestamp.class), eq("U"), eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo)));
          
      }
      
      @Test
      void testInsertOrUpdateCasOfUpdateConfigSuccess() {
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          configAdvanceInfo.put("config_tags", "tag1,tag2");
          configAdvanceInfo.put("desc", "desc11");
          configAdvanceInfo.put("use", "use2233");
          configAdvanceInfo.put("effect", "effect222");
          configAdvanceInfo.put("type", "type3");
          configAdvanceInfo.put("schema", "schema");
          String dataId = "dataId";
          String group = "group";
          String tenant = "tenant";
          String content = "content132456";
          String encryptedDataKey = "key34567";
          String casMd5 = "casMd5..";
          ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, null, content);
          configInfo.setMd5(casMd5);
          configInfo.setEncryptedDataKey(encryptedDataKey);
          
          //mock get config state,first and second is not null
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                          eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER)))
                  .thenReturn(new ConfigInfoStateWrapper(), new ConfigInfoStateWrapper());
          
          ConfigAllInfo configAllInfo = new ConfigAllInfo();
          configAllInfo.setDataId(dataId);
          configAllInfo.setGroup(group);
          configAllInfo.setTenant(tenant);
          configAllInfo.setAppName("old_app");
          configAllInfo.setMd5("old_md5");
          configAllInfo.setId(12345678765L);
          //mock get all config info
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(configAllInfo);
          
          String srcIp = "srcIp";
          String srcUser = "srcUser";
          //mock update config info cas
          Mockito.when(
                  jdbcTemplate.update(anyString(), eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)),
                          eq(srcIp), eq(srcUser), eq(configAllInfo.getAppName()), eq(configAdvanceInfo.get("desc")),
                          eq(configAdvanceInfo.get("use")), eq(configAdvanceInfo.get("effect")),
                          eq(configAdvanceInfo.get("type")), eq(configAdvanceInfo.get("schema")), eq(encryptedDataKey),
                          eq(dataId), eq(group), eq(tenant), eq(casMd5))).thenReturn(1);
          
          //mock insert config tags.
          Mockito.when(jdbcTemplate.update(
                          eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                          eq(configAllInfo.getId()), anyString(), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)))
                  .thenReturn(1);
          
          //mock insert his config info
          Mockito.doNothing().when(historyConfigInfoPersistService)
                  .insertConfigHistoryAtomic(eq(configAllInfo.getId()), eq(configInfo), eq(srcIp), eq(srcUser),
                          any(Timestamp.class), eq("I"), eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo)));
          
          externalConfigInfoPersistService.insertOrUpdateCas(srcIp, srcUser, configInfo, configAdvanceInfo);
          //expect update config cas
          Mockito.verify(jdbcTemplate, times(1))
                  .update(anyString(), eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp),
                          eq(srcUser), eq(configAllInfo.getAppName()), eq(configAdvanceInfo.get("desc")),
                          eq(configAdvanceInfo.get("use")), eq(configAdvanceInfo.get("effect")),
                          eq(configAdvanceInfo.get("type")), eq(configAdvanceInfo.get("schema")), eq(encryptedDataKey),
                          eq(dataId), eq(group), eq(tenant), eq(casMd5));
          
          //expect update config tags
          Mockito.verify(jdbcTemplate, times(1)).update(eq(
                          externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                  eq(configAllInfo.getId()), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant));
          Mockito.verify(jdbcTemplate, times(1)).update(eq(
                          externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(),
                                          TableConstant.CONFIG_TAGS_RELATION)
                                  .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))),
                  eq(configAllInfo.getId()), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant));
          
          //expect insert history info
          Mockito.verify(historyConfigInfoPersistService, times(1))
                  .insertConfigHistoryAtomic(eq(configAllInfo.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser),
                          any(Timestamp.class), eq("U"), eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo)));
          
      }
      
      @Test
      void testCreatePsForInsertConfigInfo() throws SQLException {
          
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          configAdvanceInfo.put("config_tags", "tag1,tag2");
          configAdvanceInfo.put("desc", "desc11");
          configAdvanceInfo.put("use", "use2233");
          configAdvanceInfo.put("effect", "effect222");
          configAdvanceInfo.put("type", "type3");
          configAdvanceInfo.put("schema", "schema");
          String dataId = "dataId";
          String group = "group";
          String tenant = "tenant";
          String content = "content132456";
          ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, null, content);
          Connection mockConnection = Mockito.mock(Connection.class);
          PreparedStatement preparedStatement = Mockito.mock(PreparedStatement.class);
          
          ConfigInfoMapper configInfoMapper = externalConfigInfoPersistService.mapperManager.findMapper(
                  dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO);
          
          Mockito.when(mockConnection.prepareStatement(anyString(), any(String[].class))).thenReturn(preparedStatement);
          String srcIp = "srcIp";
          String srcUser = "srcUser";
          externalConfigInfoPersistService.createPsForInsertConfigInfo(srcIp, srcUser, configInfo, configAdvanceInfo,
                  mockConnection, configInfoMapper);
          Mockito.verify(preparedStatement, times(14)).setString(anyInt(), anyString());
      }
      
      @Test
      void testRemoveConfigInfo() {
          String dataId = "dataId4567";
          String group = "group3456789";
          String tenant = "tenant4567890";
          
          //mock exist all config info
          ConfigAllInfo configAllInfo = new ConfigAllInfo();
          configAllInfo.setDataId(dataId);
          configAllInfo.setGroup(group);
          configAllInfo.setTenant(tenant);
          configAllInfo.setAppName("old_app");
          configAllInfo.setMd5("old_md5");
          configAllInfo.setId(12345678765L);
          
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(configAllInfo);
          
          String srcIp = "srcIp1234";
          String srcUser = "srcUser";
          externalConfigInfoPersistService.removeConfigInfo(dataId, group, tenant, srcIp, srcUser);
          
          //expect delete to be invoked
          Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(dataId), eq(group), eq(tenant));
          //expect delete tags to be invoked
          Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(configAllInfo.getId()));
          //expect insert delete history
          Mockito.verify(historyConfigInfoPersistService, times(1))
                  .insertConfigHistoryAtomic(eq(configAllInfo.getId()), eq(configAllInfo), eq(srcIp), eq(srcUser), any(),
                          eq("D"), eq("formal"), eq(null), eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo)));
          
      }
      
      @Test
      void testRemoveConfigInfoByIds() {
          
          //mock exist config info
          final List<ConfigAllInfo> configAllInfos = new ArrayList<>();
          final ConfigAllInfo configAllInfo1 = new ConfigAllInfo();
          final ConfigAllInfo configAllInfo2 = new ConfigAllInfo();
          configAllInfo1.setDataId("dataId1");
          configAllInfo1.setGroup("group1");
          configAllInfo1.setTenant("tenant1");
          configAllInfo1.setAppName("app1");
          configAllInfo2.setDataId("dataId2");
          configAllInfo2.setGroup("group2");
          configAllInfo2.setTenant("tenant2");
          configAllInfo2.setAppName("app2");
          configAllInfos.add(configAllInfo1);
          configAllInfos.add(configAllInfo2);
          List<Long> deleteIds = Arrays.asList(12344L, 3456789L);
          configAllInfos.get(0).setId(12344L);
          configAllInfos.get(1).setId(3456789L);
          Mockito.when(jdbcTemplate.query(anyString(), eq(deleteIds.toArray()), eq(CONFIG_ALL_INFO_ROW_MAPPER)))
                  .thenReturn(configAllInfos);
          String srcIp = "srcIp1234";
          String srcUser = "srcUser";
          externalConfigInfoPersistService.removeConfigInfoByIds(deleteIds, srcIp, srcUser);
          
          //expect delete to be invoked
          Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(deleteIds.get(0)), eq(deleteIds.get(1)));
          //expect delete tags to be invoked
          Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(deleteIds.get(0)));
          Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(deleteIds.get(1)));
          //expect insert delete history
          Mockito.verify(historyConfigInfoPersistService, times(1))
                  .insertConfigHistoryAtomic(eq(configAllInfos.get(0).getId()), eq(configAllInfos.get(0)), eq(srcIp),
                          eq(srcUser), any(), eq("D"), eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfos.get(0))));
          Mockito.verify(historyConfigInfoPersistService, times(1))
                  .insertConfigHistoryAtomic(eq(configAllInfos.get(1).getId()), eq(configAllInfos.get(1)), eq(srcIp),
                          eq(srcUser), any(), eq("D"), eq("formal"), eq(null),
                          eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfos.get(0))));
          
      }
      
      @Test
      void testBatchInsertOrUpdateOverwrite() throws NacosException {
          List<ConfigAllInfo> configInfoList = new ArrayList<>();
          //insert direct
          configInfoList.add(createMockConfigAllInfo(0));
          //exist config and overwrite
          configInfoList.add(createMockConfigAllInfo(1));
          //insert direct
          configInfoList.add(createMockConfigAllInfo(2));
          String srcUser = "srcUser1324";
          String srcIp = "srcIp1243";
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          //mock transactionTemplate and replace
          TransactionTemplate transactionTemplateCurrent = Mockito.mock(TransactionTemplate.class);
          ReflectionTestUtils.setField(externalConfigInfoPersistService, "tjt", transactionTemplateCurrent);
          //mock add config 1 success,config 2 fail and update success,config 3 success
          Mockito.when(transactionTemplateCurrent.execute(any()))
                  .thenReturn(new ConfigOperateResult(true), new ConfigOperateResult(false),
                          new ConfigOperateResult(true), new ConfigOperateResult(true));
          
          Map<String, Object> stringObjectMap = externalConfigInfoPersistService.batchInsertOrUpdate(configInfoList,
                  srcUser, srcIp, configAdvanceInfo, SameConfigPolicy.OVERWRITE);
          assertEquals(3, stringObjectMap.get("succCount"));
          assertEquals(0, stringObjectMap.get("skipCount"));
      }
      
      @Test
      void testBatchInsertOrUpdateSkip() throws NacosException {
          List<ConfigAllInfo> configInfoList = new ArrayList<>();
          //insert direct
          configInfoList.add(createMockConfigAllInfo(0));
          //exist config and overwrite
          configInfoList.add(createMockConfigAllInfo(1));
          //insert direct
          configInfoList.add(createMockConfigAllInfo(2));
          String srcUser = "srcUser1324";
          String srcIp = "srcIp1243";
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          //mock transactionTemplate and replace
          TransactionTemplate transactionTemplateCurrent = Mockito.mock(TransactionTemplate.class);
          ReflectionTestUtils.setField(externalConfigInfoPersistService, "tjt", transactionTemplateCurrent);
          //mock add config 1 success,config 2 fail and skip,config 3 success
          Mockito.when(transactionTemplateCurrent.execute(any()))
                  .thenReturn(new ConfigOperateResult(true), new ConfigOperateResult(false),
                          new ConfigOperateResult(true));
          
          Map<String, Object> stringObjectMap = externalConfigInfoPersistService.batchInsertOrUpdate(configInfoList,
                  srcUser, srcIp, configAdvanceInfo, SameConfigPolicy.SKIP);
          assertEquals(2, stringObjectMap.get("succCount"));
          assertEquals(1, stringObjectMap.get("skipCount"));
          assertEquals(configInfoList.get(1).getDataId(),
                  ((List<Map<String, String>>) stringObjectMap.get("skipData")).get(0).get("dataId"));
      }
      
      @Test
      void testBatchInsertOrUpdateAbort() throws NacosException {
          List<ConfigAllInfo> configInfoList = new ArrayList<>();
          //insert direct
          configInfoList.add(createMockConfigAllInfo(0));
          //exist config and overwrite
          configInfoList.add(createMockConfigAllInfo(1));
          //insert direct
          configInfoList.add(createMockConfigAllInfo(2));
          String srcUser = "srcUser1324";
          String srcIp = "srcIp1243";
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          //mock transactionTemplate and replace
          TransactionTemplate transactionTemplateCurrent = Mockito.mock(TransactionTemplate.class);
          ReflectionTestUtils.setField(externalConfigInfoPersistService, "tjt", transactionTemplateCurrent);
          //mock add config 1 success,config 2 fail and abort,config 3 not operated
          Mockito.when(transactionTemplateCurrent.execute(any()))
                  .thenReturn(new ConfigOperateResult(true), new ConfigOperateResult(false));
          
          Map<String, Object> stringObjectMap = externalConfigInfoPersistService.batchInsertOrUpdate(configInfoList,
                  srcUser, srcIp, configAdvanceInfo, SameConfigPolicy.ABORT);
          assertEquals(1, stringObjectMap.get("succCount"));
          assertEquals(1, stringObjectMap.get("skipCount"));
          // config 2 failed
          assertEquals(configInfoList.get(1).getDataId(),
                  ((List<Map<String, String>>) stringObjectMap.get("failData")).get(0).get("dataId"));
          //skip config 3
          assertEquals(configInfoList.get(2).getDataId(),
                  ((List<Map<String, String>>) stringObjectMap.get("skipData")).get(0).get("dataId"));
      }
      
      private ConfigAllInfo createMockConfigAllInfo(long mockId) {
          ConfigAllInfo configAllInfo = new ConfigAllInfo();
          configAllInfo.setDataId("test" + mockId + ".yaml");
          configAllInfo.setGroup("test");
          configAllInfo.setCreateIp("localhost");
          configAllInfo.setCreateUser("test");
          configAllInfo.setContent("23456789000content");
          return configAllInfo;
      }
      
      private ConfigInfoWrapper createMockConfigInfoWrapper(long mockId) {
          ConfigInfoWrapper configAllInfo = new ConfigInfoWrapper();
          configAllInfo.setDataId("test" + mockId + ".yaml");
          configAllInfo.setGroup("test");
          configAllInfo.setContent("23456789000content");
          return configAllInfo;
      }
      
      private ConfigInfoStateWrapper createMockConfigInfoStateWrapper(long mockId) {
          ConfigInfoStateWrapper configAllInfo = new ConfigInfoStateWrapper();
          configAllInfo.setDataId("test" + mockId + ".yaml");
          configAllInfo.setGroup("test");
          configAllInfo.setLastModified(System.currentTimeMillis());
          return configAllInfo;
      }
      
      private ConfigInfo createMockConfigInfo(long mockId) {
          ConfigInfo configInfo = new ConfigInfo();
          configInfo.setDataId("test" + mockId + ".yaml");
          configInfo.setGroup("test");
          configInfo.setContent("23456789000content");
          
          return configInfo;
      }
      
      @Test
      void testFindConfigMaxId() {
          
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(Long.class))).thenReturn(123456L);
          long configMaxId = externalConfigInfoPersistService.findConfigMaxId();
          assertEquals(123456L, configMaxId);
      }
      
      @Test
      void testFindConfigMaxId0() {
          
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(Long.class))).thenThrow(new NullPointerException());
          long configMaxId = externalConfigInfoPersistService.findConfigMaxId();
          assertEquals(0, configMaxId);
      }
      
      @Test
      void testFindConfigInfoById() {
          long id = 1234567890876L;
          ConfigInfo configInfo = new ConfigInfo();
          configInfo.setId(id);
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {id}), eq(CONFIG_INFO_ROW_MAPPER)))
                  .thenReturn(configInfo);
          ConfigInfo configReturn = externalConfigInfoPersistService.findConfigInfo(id);
          assertEquals(id, configReturn.getId());
      }
      
      @Test
      void testFindConfigInfoByIdNull() {
          long id = 1234567890876L;
          ConfigInfo configInfo = new ConfigInfo();
          configInfo.setId(id);
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {id}), eq(CONFIG_INFO_ROW_MAPPER)))
                  .thenThrow(new EmptyResultDataAccessException(1));
          ConfigInfo configReturn = externalConfigInfoPersistService.findConfigInfo(id);
          assertNull(configReturn);
      }
      
      @Test
      void testFindConfigInfoByIdGetConFail() {
          long id = 1234567890876L;
          ConfigInfo configInfo = new ConfigInfo();
          configInfo.setId(id);
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {id}), eq(CONFIG_INFO_ROW_MAPPER)))
                  .thenThrow(new CannotGetJdbcConnectionException("mocked exp"));
          try {
              ConfigInfo configReturn = externalConfigInfoPersistService.findConfigInfo(id);
              assertTrue(false);
          } catch (Exception e) {
              assertTrue(e instanceof CannotGetJdbcConnectionException);
          }
      }
      
      @Test
      void testFindConfigInfoByDataId() {
          String dataId = "dataId4567";
          String group = "group3456789";
          String tenant = "tenant4567890";
          ConfigInfoWrapper configInfoWrapper = new ConfigInfoWrapper();
          configInfoWrapper.setDataId(dataId);
          configInfoWrapper.setGroup(group);
          configInfoWrapper.setTenant(tenant);
          
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(configInfoWrapper);
          ConfigInfo configReturn = externalConfigInfoPersistService.findConfigInfo(dataId, group, tenant);
          assertEquals(dataId, configReturn.getDataId());
      }
      
      @Test
      void testFindConfigInfoByDataIdNull() {
          String dataId = "dataId4567";
          String group = "group3456789";
          String tenant = "tenant4567890";
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenThrow(new EmptyResultDataAccessException(1));
          ConfigInfoWrapper configReturn = externalConfigInfoPersistService.findConfigInfo(dataId, group, tenant);
          assertNull(configReturn);
      }
      
      @Test
      void testFindConfigInfoByDataIdGetConFail() {
          String dataId = "dataId4567222";
          String group = "group3456789";
          String tenant = "tenant4567890";
          
          Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenThrow(new CannotGetJdbcConnectionException("mocked exp"));
          try {
              externalConfigInfoPersistService.findConfigInfo(dataId, group, tenant);
              assertTrue(false);
          } catch (Exception e) {
              assertTrue(e instanceof CannotGetJdbcConnectionException);
          }
      }
      
      @Test
      void testFindConfigInfo4Page() {
          String dataId = "dataId4567222";
          String group = "group3456789";
          String tenant = "tenant4567890";
          
          //mock total count
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {tenant, dataId, group}),
                  eq(Integer.class))).thenReturn(new Integer(9));
          //mock page list
          List<ConfigInfo> result = new ArrayList<>();
          result.add(createMockConfigInfo(0));
          result.add(createMockConfigInfo(1));
          result.add(createMockConfigInfo(2));
          when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant, dataId, group}),
                  eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result);
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          Page<ConfigInfo> configInfo4Page = externalConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group,
                  tenant, configAdvanceInfo);
          assertEquals(result.size(), configInfo4Page.getPageItems().size());
          assertEquals(9, configInfo4Page.getTotalCount());
          
      }
      
      @Test
      void testFindConfigInfo4PageWithTags() {
          String dataId = "dataId4567222";
          String group = "group3456789";
          String tenant = "tenant4567890";
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          configAdvanceInfo.put("config_tags", "tags1,tags3");
          
          //mock total count
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {tenant, dataId, group, "tags1", "tags3"}),
                  eq(Integer.class))).thenReturn(new Integer(9));
          //mock page list
          List<ConfigInfo> result = new ArrayList<>();
          result.add(createMockConfigInfo(0));
          result.add(createMockConfigInfo(1));
          result.add(createMockConfigInfo(2));
          when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant, dataId, group, "tags1", "tags3"}),
                  eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result);
          
          Page<ConfigInfo> configInfo4Page = externalConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group,
                  tenant, configAdvanceInfo);
          assertEquals(result.size(), configInfo4Page.getPageItems().size());
          assertEquals(9, configInfo4Page.getTotalCount());
      }
      
      @Test
      void testConfigInfoCount() {
          
          //mock total count
          when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class))).thenReturn(new Integer(9));
          int count = externalConfigInfoPersistService.configInfoCount();
          assertEquals(9, count);
          
          when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class))).thenReturn(null);
          try {
              externalConfigInfoPersistService.configInfoCount();
              assertTrue(false);
          } catch (Exception e) {
              assertTrue(e instanceof IllegalArgumentException);
          }
          
      }
      
      @Test
      void testConfigInfoCountByTenant() {
          
          String tenant = "tenant124";
          //mock total count
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {tenant}), eq(Integer.class))).thenReturn(
                  new Integer(90));
          int count = externalConfigInfoPersistService.configInfoCount(tenant);
          assertEquals(90, count);
          
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {tenant}), eq(Integer.class))).thenReturn(null);
          try {
              externalConfigInfoPersistService.configInfoCount(tenant);
              assertTrue(false);
          } catch (Exception e) {
              assertTrue(e instanceof IllegalArgumentException);
          }
          
      }
      
      @Test
      void testFindConfigInfoLike4Page() {
          String dataId = "dataId4567222*";
          String group = "group3456789*";
          String tenant = "tenant4567890";
          String appName = "appName1234";
          String content = "content123";
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          configAdvanceInfo.put("appName", appName);
          configAdvanceInfo.put("content", content);
          //mock total count
          when(jdbcTemplate.queryForObject(anyString(),
                  eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName,
                          content}), eq(Integer.class))).thenReturn(new Integer(9));
          //mock page list
          List<ConfigInfo> result = new ArrayList<>();
          result.add(createMockConfigInfo(0));
          result.add(createMockConfigInfo(1));
          result.add(createMockConfigInfo(2));
          when(jdbcTemplate.query(anyString(),
                  eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName,
                          content}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result);
          
          Page<ConfigInfo> configInfo4Page = externalConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group,
                  tenant, configAdvanceInfo);
          assertEquals(result.size(), configInfo4Page.getPageItems().size());
          assertEquals(9, configInfo4Page.getTotalCount());
          
      }
      
      @Test
      void testFindConfigInfoLike4PageWithTags() {
          
          String appName = "appName1234";
          String content = "content123";
          Map<String, Object> configAdvanceInfo = new HashMap<>();
          configAdvanceInfo.put("appName", appName);
          configAdvanceInfo.put("content", content);
          configAdvanceInfo.put("config_tags", "tags,tag2");
          String dataId = "dataId4567222*";
          String group = "group3456789*";
          String tenant = "tenant4567890";
          //mock total count
          when(jdbcTemplate.queryForObject(anyString(),
                  eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content,
                          "tags", "tag2"}), eq(Integer.class))).thenReturn(new Integer(9));
          //mock page list
          List<ConfigInfo> result = new ArrayList<>();
          result.add(createMockConfigInfo(0));
          result.add(createMockConfigInfo(1));
          result.add(createMockConfigInfo(2));
          when(jdbcTemplate.query(anyString(),
                  eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content,
                          "tags", "tag2"}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result);
          
          Page<ConfigInfo> configInfo4Page = externalConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group,
                  tenant, configAdvanceInfo);
          assertEquals(result.size(), configInfo4Page.getPageItems().size());
          assertEquals(9, configInfo4Page.getTotalCount());
          
      }
      
      @Test
      void testFindChangeConfig() {
          
          //mock page list
          List<ConfigInfoStateWrapper> result = new ArrayList<>();
          result.add(createMockConfigInfoStateWrapper(0));
          result.add(createMockConfigInfoStateWrapper(1));
          result.add(createMockConfigInfoStateWrapper(2));
          Timestamp startTime = new Timestamp(System.currentTimeMillis() - 1000L);
          long lastMaxId = 10000L;
          int pageSize = 30;
          when(jdbcTemplate.query(anyString(), eq(new Object[] {startTime, lastMaxId, pageSize}),
                  eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(result);
          
          List<ConfigInfoStateWrapper> configInfo4List = externalConfigInfoPersistService.findChangeConfig(startTime,
                  lastMaxId, pageSize);
          assertEquals(result.size(), configInfo4List.size());
      }
      
      @Test
      void testFindChangeConfigError() {
          Timestamp startTime = new Timestamp(System.currentTimeMillis() - 1000L);
          long lastMaxId = 10000L;
          int pageSize = 30;
          //mock page list
          when(jdbcTemplate.query(anyString(), eq(new Object[] {startTime, lastMaxId, pageSize}),
                  eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenThrow(new CannotAcquireLockException("mock ex"));
          try {
              List<ConfigInfoStateWrapper> configInfo4List = externalConfigInfoPersistService.findChangeConfig(startTime,
                      lastMaxId, pageSize);
              assertTrue(false);
          } catch (Exception e) {
              assertTrue(e instanceof CannotAcquireLockException);
          }
      }
      
      @Test
      void testSelectTagByConfig() {
          String dataId = "dataId4567222";
          String group = "group3456789";
          String tenant = "tenant4567890";
          
          //mock page list
          List<String> tagStrings = Arrays.asList("", "", "");
          when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(String.class))).thenReturn(tagStrings);
          List<String> configTags = externalConfigInfoPersistService.selectTagByConfig(dataId, group, tenant);
          assertEquals(tagStrings, configTags);
          
          //mock EmptyResultDataAccessException
          when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(String.class))).thenThrow(new EmptyResultDataAccessException(3));
          List<String> nullResult = externalConfigInfoPersistService.selectTagByConfig(dataId, group, tenant);
          assertTrue(nullResult == null);
          //mock IncorrectResultSizeDataAccessException
          when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(String.class))).thenThrow(new IncorrectResultSizeDataAccessException(3));
          List<String> nullResult2 = externalConfigInfoPersistService.selectTagByConfig(dataId, group, tenant);
          assertTrue(nullResult2 == null);
          
          //mock IncorrectResultSizeDataAccessException
          when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(String.class))).thenThrow(new CannotGetJdbcConnectionException("mock exp"));
          try {
              externalConfigInfoPersistService.selectTagByConfig(dataId, group, tenant);
              assertFalse(true);
          } catch (Exception e) {
              assertTrue(e instanceof CannotGetJdbcConnectionException);
          }
      }
      
      @Test
      void testFindConfigInfosByIds() {
          
          //mock page list
          List<ConfigInfo> result = new ArrayList<>();
          result.add(createMockConfigInfo(0));
          result.add(createMockConfigInfo(1));
          result.add(createMockConfigInfo(2));
          when(jdbcTemplate.query(anyString(), eq(new Object[] {123L, 1232345L}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(
                  result);
          String ids = "123,1232345";
          List<ConfigInfo> configInfosByIds = externalConfigInfoPersistService.findConfigInfosByIds(ids);
          assertEquals(result.size(), configInfosByIds.size());
          assertEquals(result.get(2).getDataId(), configInfosByIds.get(2).getDataId());
          
          //mock EmptyResultDataAccessException
          when(jdbcTemplate.query(anyString(), eq(new Object[] {123L, 1232345L}), eq(CONFIG_INFO_ROW_MAPPER))).thenThrow(
                  new EmptyResultDataAccessException(3));
          List<ConfigInfo> nullResult2 = externalConfigInfoPersistService.findConfigInfosByIds(ids);
          assertTrue(nullResult2 == null);
          
          //blank ids.
          List<ConfigInfo> nullResultBlankIds = externalConfigInfoPersistService.findConfigInfosByIds("");
          assertTrue(nullResultBlankIds == null);
          
          //mock CannotGetJdbcConnectionException
          when(jdbcTemplate.query(anyString(), eq(new Object[] {123L, 1232345L}), eq(CONFIG_INFO_ROW_MAPPER))).thenThrow(
                  new CannotGetJdbcConnectionException("mock exp"));
          try {
              externalConfigInfoPersistService.findConfigInfosByIds(ids);
              assertFalse(true);
          } catch (Exception e) {
              assertTrue(e instanceof CannotGetJdbcConnectionException);
          }
      }
      
      @Test
      void testFindConfigAdvanceInfo() {
          
          String dataId = "dataId1324";
          String group = "group23546";
          String tenant = "tenant13245";
          //mock select tags
          List<String> mockTags = Arrays.asList("tag1", "tag2", "tag3");
          when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(String.class))).thenReturn(mockTags);
          
          String schema = "schema12345654";
          //mock select config advance
          ConfigAdvanceInfo mockedAdvance = new ConfigAdvanceInfo();
          mockedAdvance.setSchema(schema);
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_ADVANCE_INFO_ROW_MAPPER))).thenReturn(mockedAdvance);
          
          //execute return mock obj
          ConfigAdvanceInfo configAdvanceInfo = externalConfigInfoPersistService.findConfigAdvanceInfo(dataId, group,
                  tenant);
          //expect check schema & tags.
          assertEquals(mockedAdvance.getSchema(), configAdvanceInfo.getSchema());
          assertEquals(String.join(",", mockTags), configAdvanceInfo.getConfigTags());
          
          //mock EmptyResultDataAccessException
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_ADVANCE_INFO_ROW_MAPPER))).thenThrow(new EmptyResultDataAccessException(1));
          //expect return null.
          assertNull(externalConfigInfoPersistService.findConfigAdvanceInfo(dataId, group, tenant));
          
          //mock CannotGetJdbcConnectionException
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_ADVANCE_INFO_ROW_MAPPER))).thenThrow(new CannotGetJdbcConnectionException("mock exp"));
          //expect throw exception.
          try {
              externalConfigInfoPersistService.findConfigAdvanceInfo(dataId, group, tenant);
              assertFalse(true);
          } catch (Exception e) {
              assertTrue(e instanceof CannotGetJdbcConnectionException);
              assertTrue(e.getMessage().endsWith("mock exp"));
          }
      }
      
      @Test
      void testFindConfigAllInfo() {
          
          String dataId = "dataId1324";
          String group = "group23546";
          String tenant = "tenant13245";
          //mock select tags
          List<String> mockTags = Arrays.asList("tag1", "tag2", "tag3");
          when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(String.class))).thenReturn(mockTags);
          
          String schema = "schema12345654";
          //mock select config advance
          ConfigAllInfo mockedConfig = new ConfigAllInfo();
          mockedConfig.setSchema(schema);
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockedConfig);
          
          //execute return mock obj
          ConfigAllInfo configAllInfo = externalConfigInfoPersistService.findConfigAllInfo(dataId, group, tenant);
          //expect check schema & tags.
          assertEquals(mockedConfig.getSchema(), configAllInfo.getSchema());
          assertEquals(String.join(",", mockTags), configAllInfo.getConfigTags());
          
          //mock EmptyResultDataAccessException
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenThrow(new EmptyResultDataAccessException(1));
          //expect return null.
          assertNull(externalConfigInfoPersistService.findConfigAllInfo(dataId, group, tenant));
          
          //mock CannotGetJdbcConnectionException
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenThrow(new CannotGetJdbcConnectionException("mock exp"));
          //expect throw exception.
          try {
              externalConfigInfoPersistService.findConfigAllInfo(dataId, group, tenant);
              assertFalse(true);
          } catch (Exception e) {
              assertTrue(e instanceof CannotGetJdbcConnectionException);
              assertTrue(e.getMessage().endsWith("mock exp"));
          }
      }
      
      @Test
      void testFindConfigInfoState() {
          
          String dataId = "dataId1324";
          String group = "group23546";
          String tenant = "tenant13245";
          
          //mock select config state
          ConfigInfoStateWrapper mockedConfig = new ConfigInfoStateWrapper();
          mockedConfig.setLastModified(2345678L);
          mockedConfig.setId(23456789098765L);
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(mockedConfig);
          
          //execute return mock obj
          ConfigInfoStateWrapper configInfoStateWrapper = externalConfigInfoPersistService.findConfigInfoState(dataId,
                  group, tenant);
          //expect check schema & tags.
          assertEquals(mockedConfig.getId(), configInfoStateWrapper.getId());
          assertEquals(mockedConfig.getLastModified(), configInfoStateWrapper.getLastModified());
          
          //mock EmptyResultDataAccessException
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenThrow(new EmptyResultDataAccessException(1));
          //expect return null.
          assertNull(externalConfigInfoPersistService.findConfigInfoState(dataId, group, tenant));
          
          //mock CannotGetJdbcConnectionException
          when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}),
                  eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenThrow(new CannotGetJdbcConnectionException("mock exp"));
          //expect throw exception.
          try {
              externalConfigInfoPersistService.findConfigInfoState(dataId, group, tenant);
              assertFalse(true);
          } catch (Exception e) {
              assertTrue(e instanceof CannotGetJdbcConnectionException);
              assertTrue(e.getMessage().endsWith("mock exp"));
          }
      }
      
      @Test
      void testFindAllConfigInfo4Export() {
          
          //mock select config state
          List<ConfigAllInfo> mockConfigs = new ArrayList<>();
          mockConfigs.add(createMockConfigAllInfo(0));
          mockConfigs.add(createMockConfigAllInfo(1));
          mockConfigs.add(createMockConfigAllInfo(2));
          
          String dataId = "dataId1324";
          String group = "group23546";
          String tenant = "tenant13245";
          String appName = "appName1243";
          List<Long> ids = Arrays.asList(132L, 1343L, 245L);
          
          when(jdbcTemplate.query(anyString(), eq(new Object[] {132L, 1343L, 245L}),
                  eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockConfigs);
          //execute return mock obj
          List<ConfigAllInfo> configAllInfosIds = externalConfigInfoPersistService.findAllConfigInfo4Export(dataId, group,
                  tenant, appName, ids);
          //expect check
          assertEquals(mockConfigs, configAllInfosIds);
          
          when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant, dataId, group, appName}),
                  eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockConfigs);
          //execute return mock obj
          List<ConfigAllInfo> configAllInfosWithDataId = externalConfigInfoPersistService.findAllConfigInfo4Export(dataId,
                  group, tenant, appName, null);
          //expect check
          assertEquals(mockConfigs, configAllInfosWithDataId);
          
          //mock CannotGetJdbcConnectionException
          when(jdbcTemplate.query(anyString(), eq(new Object[] {132L, 1343L, 245L}),
                  eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenThrow(new CannotGetJdbcConnectionException("mock exp11"));
          //expect throw exception.
          try {
              externalConfigInfoPersistService.findAllConfigInfo4Export(dataId, group, tenant, appName, ids);
              assertFalse(true);
          } catch (Exception e) {
              assertTrue(e instanceof CannotGetJdbcConnectionException);
              assertTrue(e.getMessage().endsWith("mock exp11"));
          }
      }
      
      @Test
      void testQueryConfigInfoByNamespace() {
          
          //mock select config state
          List<ConfigInfoWrapper> mockConfigs = new ArrayList<>();
          mockConfigs.add(createMockConfigInfoWrapper(0));
          mockConfigs.add(createMockConfigInfoWrapper(1));
          mockConfigs.add(createMockConfigInfoWrapper(2));
          String tenant = "tenant13245";
          when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(
                  mockConfigs);
          //execute return mock obj
          List<ConfigInfoWrapper> configInfoWrappers = externalConfigInfoPersistService.queryConfigInfoByNamespace(
                  tenant);
          //expect check
          assertEquals(mockConfigs, configInfoWrappers);
          
          //mock CannotGetJdbcConnectionException
          when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenThrow(
                  new EmptyResultDataAccessException(2));
          //execute return mock obj
          List<ConfigInfoWrapper> configInfoWrapperNull = externalConfigInfoPersistService.queryConfigInfoByNamespace(
                  tenant);
          //expect check
          assertEquals(Collections.EMPTY_LIST, configInfoWrapperNull);
          
          //mock CannotGetJdbcConnectionException
          when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenThrow(
                  new CannotGetJdbcConnectionException("mock exp1111"));
          //expect throw exception.
          try {
              externalConfigInfoPersistService.queryConfigInfoByNamespace(tenant);
              assertFalse(true);
          } catch (Exception e) {
              assertTrue(e instanceof CannotGetJdbcConnectionException);
              assertTrue(e.getMessage().endsWith("mock exp1111"));
          }
      }
      
      @Test
      void testGetTenantIdList() {
          
          int page = 10;
          int pageSize = 100;
          //mock select config state
          List<String> tenantStrings = Arrays.asList("tenant1", "tenant2", "tenant3");
          when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {}), eq(String.class))).thenReturn(tenantStrings);
          //execute return mock obj
          List<String> returnTenants = externalConfigInfoPersistService.getTenantIdList(page, pageSize);
          
          //expect check
          assertEquals(tenantStrings, returnTenants);
      }
      
      @Test
      void testGetGroupIdList() {
          
          int page = 10;
          int pageSize = 100;
          //mock select config state
          List<String> groupStrings = Arrays.asList("group1", "group2", "group3");
          when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {}), eq(String.class))).thenReturn(groupStrings);
          //execute return mock obj
          List<String> returnGroups = externalConfigInfoPersistService.getGroupIdList(page, pageSize);
          
          //expect check
          assertEquals(groupStrings, returnGroups);
      }
      
      @Test
      void testFindAllConfigInfoFragment() {
          //mock page list
          List<ConfigInfoWrapper> mockConfigs = new ArrayList<>();
          mockConfigs.add(createMockConfigInfoWrapper(0));
          mockConfigs.add(createMockConfigInfoWrapper(1));
          mockConfigs.add(createMockConfigInfoWrapper(2));
          long lastId = 10111L;
          when(jdbcTemplate.query(anyString(), eq(new Object[] {lastId}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(
                  mockConfigs);
          int pageSize = 100;
          //execute return mock obj
          Page<ConfigInfoWrapper> returnConfigPage = externalConfigInfoPersistService.findAllConfigInfoFragment(lastId,
                  pageSize, true);
          
          //expect check
          assertEquals(mockConfigs, returnConfigPage.getPageItems());
          
          when(jdbcTemplate.query(anyString(), eq(new Object[] {lastId}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenThrow(
                  new CannotGetJdbcConnectionException("mock fail"));
          try {
              externalConfigInfoPersistService.findAllConfigInfoFragment(lastId, pageSize, true);
              assertTrue(false);
          } catch (Exception e) {
              assertEquals("mock fail", e.getMessage());
          }
          
      }
      
      @Test
      void testBuildFindConfigInfoStateSql() {
          MapperManager mapperManager = MapperManager.instance(false);
          ConfigInfoMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(),
                  TableConstant.CONFIG_INFO);
          String select = configInfoMapper.select(Arrays.asList("id", "data_id", "group_id", "tenant_id", "gmt_modified"),
                  Arrays.asList("data_id", "group_id", "tenant_id"));
          assertEquals(
                  "SELECT id,data_id,group_id,tenant_id,gmt_modified FROM config_info WHERE data_id = ? AND group_id = ? AND tenant_id = ?",
                  select);
      }
      
  }
  