Back to Repositories

Validating SQL Script Conversion and Database Migration Workflow in Apollo Config

This test suite validates the SQL conversion functionality for Apollo’s H2 database implementation. It ensures proper conversion of MySQL scripts to H2-compatible format and verifies the execution order of migration scripts.

Test Coverage Overview

The test suite provides comprehensive coverage of SQL conversion and database initialization processes.

  • Validates conversion of MySQL scripts to H2 format
  • Tests script execution ordering for database migrations
  • Verifies both config and portal database initializations
  • Covers file path handling and directory management

Implementation Analysis

The testing approach employs JUnit 5 framework with systematic validation of SQL conversion processes.

Key patterns include:
  • Sequential script execution verification
  • In-memory H2 database testing
  • Resource management using Spring JDBC utilities
  • Custom H2 function implementations

Technical Details

Testing infrastructure includes:

  • H2 in-memory database with MySQL compatibility mode
  • Spring JDBC for database operations
  • ResourceDatabasePopulator for script execution
  • Custom SQL template processing
  • File system operations for SQL script management

Best Practices Demonstrated

The test implementation showcases several testing best practices:

  • Isolated database testing using in-memory instances
  • Proper resource cleanup and management
  • Systematic validation of execution order
  • Clear separation of config and portal database tests
  • Comprehensive error handling with assertions

apolloconfig/apollo

apollo-build-sql-converter/src/test/java/com/ctrip/framework/apollo/build/sql/converter/ApolloSqlConverterH2Test.java

            
/*
 * Copyright 2024 Apollo Authors
 *
 * 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.ctrip.framework.apollo.build.sql.converter;

import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.PathResource;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;

class ApolloSqlConverterH2Test {

  @Test
  void checkH2() {
    String repositoryDir = ApolloSqlConverterUtil.getRepositoryDir();

    String srcDir = repositoryDir + "/scripts/sql/src";
    String checkerParentDir =
        repositoryDir + "/apollo-build-sql-converter/target/scripts/sql/checker-h2";

    String testSrcDir =
        repositoryDir + "/apollo-build-sql-converter/src/test/resources/META-INF/sql/h2-test";
    String testCheckerParentDir =
        repositoryDir + "/apollo-build-sql-converter/target/scripts/sql/checker-h2-test";

    // generate checker sql files
    ApolloSqlConverterUtil.deleteDir(Paths.get(checkerParentDir));
    SqlTemplateGist gists = ApolloSqlConverterUtil.getGists(repositoryDir);
    SqlTemplateGist h2TestGist = gists.toBuilder()
        .h2Function("\n"
            + "\n"
            + "-- H2 Function\n"
            + "-- ------------------------------------------------------------\n"
            + "CREATE ALIAS IF NOT EXISTS UNIX_TIMESTAMP FOR \"com.ctrip.framework.apollo.build.sql.converter.TestH2Function.unixTimestamp\";\n")
        .build();
    List<String> srcSqlList = ApolloSqlConverter.convert(repositoryDir, srcDir, checkerParentDir,
        h2TestGist);
    List<String> checkerSqlList = new ArrayList<>(srcSqlList.size());
    for (String srcSql : srcSqlList) {
      String checkerSql = ApolloSqlConverterUtil.replacePath(srcSql, srcDir,
          checkerParentDir + "/sql/profiles/h2-default");
      checkerSqlList.add(checkerSql);
    }

    // generate test checker sql files
    ApolloSqlConverterUtil.deleteDir(Paths.get(testCheckerParentDir));
    List<String> testSrcSqlList = ApolloSqlConverter.convert(repositoryDir, testSrcDir,
        testCheckerParentDir, h2TestGist);
    List<String> testCheckerSqlList = new ArrayList<>(testSrcSqlList.size());
    for (String testSrcSql : testSrcSqlList) {
      String testCheckerSql = ApolloSqlConverterUtil.replacePath(testSrcSql, testSrcDir,
          testCheckerParentDir + "/sql/profiles/h2-default");
      testCheckerSqlList.add(testCheckerSql);
    }

    this.checkSort(testSrcSqlList);

    String h2Path = "test-h2";
    this.checkConfigDatabase(h2Path, checkerSqlList, testCheckerSqlList);

    this.checkPortalDatabase(h2Path, checkerSqlList, testCheckerSqlList);

  }

  private void checkSort(List<String> testSrcSqlList) {
    int baseIndex = this.getIndex(testSrcSqlList, "apolloconfigdb-v000-v010-base.sql");
    Assertions.assertTrue(baseIndex >= 0);
    int beforeIndex = this.getIndex(testSrcSqlList, "apolloconfigdb-v000-v010-before.sql");
    Assertions.assertTrue(beforeIndex >= 0);
    int deltaIndex = this.getIndex(testSrcSqlList, "apolloconfigdb-v000-v010.sql");
    Assertions.assertTrue(deltaIndex >= 0);
    int afterIndex = this.getIndex(testSrcSqlList, "apolloconfigdb-v000-v010-after.sql");
    Assertions.assertTrue(afterIndex >= 0);

    // base < before < delta < after
    Assertions.assertTrue(baseIndex < beforeIndex);
    Assertions.assertTrue(beforeIndex < deltaIndex);
    Assertions.assertTrue(deltaIndex < afterIndex);
  }

  private int getIndex(List<String> srcSqlList, String fileName) {
    for (int i = 0; i < srcSqlList.size(); i++) {
      String sqlFile = srcSqlList.get(i);
      if (sqlFile.endsWith(fileName)) {
        return i;
      }
    }
    return -1;
  }

  private void checkConfigDatabase(String h2Path, List<String> checkerSqlList,
      List<String> testCheckerSqlList) {
    SimpleDriverDataSource configDataSource = new SimpleDriverDataSource();
    configDataSource.setUrl("jdbc:h2:mem:~/" + h2Path
        + "/apollo-config-db;mode=mysql;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;BUILTIN_ALIAS_OVERRIDE=TRUE;DATABASE_TO_UPPER=FALSE");
    configDataSource.setDriverClass(org.h2.Driver.class);

    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.setContinueOnError(false);
    populator.setSeparator(";");
    populator.setSqlScriptEncoding(StandardCharsets.UTF_8.name());

    for (String sqlFile : testCheckerSqlList) {
      if (sqlFile.contains("apolloconfigdb-")) {
        populator.addScript(new PathResource(Paths.get(sqlFile)));
      }
    }

    for (String sqlFile : checkerSqlList) {
      if (sqlFile.contains("apolloconfigdb-")) {
        populator.addScript(new PathResource(Paths.get(sqlFile)));
      }
    }

    DatabasePopulatorUtils.execute(populator, configDataSource);
  }

  private void checkPortalDatabase(String h2Path, List<String> checkerSqlList,
      List<String> testCheckerSqlList) {
    SimpleDriverDataSource portalDataSource = new SimpleDriverDataSource();
    portalDataSource.setUrl("jdbc:h2:mem:~/" + h2Path
        + "/apollo-portal-db;mode=mysql;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;BUILTIN_ALIAS_OVERRIDE=TRUE;DATABASE_TO_UPPER=FALSE");
    portalDataSource.setDriverClass(org.h2.Driver.class);

    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.setContinueOnError(false);
    populator.setSeparator(";");
    populator.setSqlScriptEncoding(StandardCharsets.UTF_8.name());

    for (String sqlFile : testCheckerSqlList) {
      if (sqlFile.contains("apolloportaldb-")) {
        populator.addScript(new PathResource(Paths.get(sqlFile)));
      }
    }

    for (String sqlFile : checkerSqlList) {
      if (sqlFile.contains("apolloportaldb-")) {
        populator.addScript(new PathResource(Paths.get(sqlFile)));
      }
    }

    DatabasePopulatorUtils.execute(populator, portalDataSource);
  }

}