|
| 1 | +/* |
| 2 | + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. |
| 3 | + */ |
| 4 | + |
| 5 | +package io.airbyte.config.persistence; |
| 6 | + |
| 7 | +import io.airbyte.db.Database; |
| 8 | +import io.airbyte.db.factory.DSLContextFactory; |
| 9 | +import io.airbyte.db.factory.DataSourceFactory; |
| 10 | +import io.airbyte.db.factory.FlywayFactory; |
| 11 | +import io.airbyte.db.init.DatabaseInitializationException; |
| 12 | +import io.airbyte.db.instance.configs.ConfigsDatabaseMigrator; |
| 13 | +import io.airbyte.db.instance.configs.ConfigsDatabaseTestProvider; |
| 14 | +import io.airbyte.test.utils.DatabaseConnectionHelper; |
| 15 | +import java.io.IOException; |
| 16 | +import java.sql.SQLException; |
| 17 | +import javax.sql.DataSource; |
| 18 | +import org.flywaydb.core.Flyway; |
| 19 | +import org.jooq.DSLContext; |
| 20 | +import org.jooq.SQLDialect; |
| 21 | +import org.junit.jupiter.api.AfterAll; |
| 22 | +import org.junit.jupiter.api.BeforeAll; |
| 23 | +import org.testcontainers.containers.PostgreSQLContainer; |
| 24 | + |
| 25 | +/** |
| 26 | + * This class exists to abstract away the lifecycle of the test container database and the config |
| 27 | + * database schema. This is ALL it intends to do. Any additional functionality belongs somewhere |
| 28 | + * else. It is useful for test suites that need to interact directly with the database. |
| 29 | + * |
| 30 | + * This class sets up a test container database and runs the config database migrations against it |
| 31 | + * to provide the most up-to-date schema. |
| 32 | + * |
| 33 | + * What this class is NOT designed to do: |
| 34 | + * <ul> |
| 35 | + * <li>test migration behavior, only should be used to test query behavior against the current |
| 36 | + * schema.</li> |
| 37 | + * <li>expose database details -- if you are attempting to expose container, dataSource, dslContext, |
| 38 | + * something is wrong.</li> |
| 39 | + * <li>add test fixtures or helpers--do NOT put "generic" resource helper methods (e.g. |
| 40 | + * createTestSource())</li> |
| 41 | + * </ul> |
| 42 | + * |
| 43 | + * This comment is emphatically worded, because it is tempting to add things to this class. It has |
| 44 | + * already happened in 3 previous iterations, and each time it takes multiple engineering days to |
| 45 | + * fix it. |
| 46 | + * |
| 47 | + * Usage: |
| 48 | + * <ul> |
| 49 | + * <li>Extend: Extend this class. By doing so, it will automatically create the test container db |
| 50 | + * and run migrations against it at the start of the test suite (@BeforeAll).</li> |
| 51 | + * <li>Use database: As part of the @BeforeAll the database field is set. This is the only field |
| 52 | + * that the extending class can access. It's lifecycle is fully managed by this class.</li> |
| 53 | + * <li>Reset schema: To reset the database in between tests, call truncateAllTables() as part |
| 54 | + * of @BeforeEach. This is the only method that this class exposes externally. It is exposed in such |
| 55 | + * a way, because most test suites need to declare their own @BeforeEach, so it is easier for them |
| 56 | + * to simply call this method there, then trying to apply a more complex inheritance scheme.</li> |
| 57 | + * </ul> |
| 58 | + * |
| 59 | + * Note: truncateAllTables() works by truncating each table in the db, if you add a new table, you |
| 60 | + * will need to add it to that method for it work as expected. |
| 61 | + */ |
| 62 | +@SuppressWarnings({"PMD.MutableStaticState", "PMD.SignatureDeclareThrowsException"}) |
| 63 | +class BaseConfigDatabaseTest { |
| 64 | + |
| 65 | + static Database database; |
| 66 | + |
| 67 | + // keep these private, do not expose outside this class! |
| 68 | + private static PostgreSQLContainer<?> container; |
| 69 | + private static DataSource dataSource; |
| 70 | + private static DSLContext dslContext; |
| 71 | + |
| 72 | + /** |
| 73 | + * Create db test container, sets up java database resources, and runs migrations. Should not be |
| 74 | + * called externally. It is not private because junit cannot access private methods. |
| 75 | + * |
| 76 | + * @throws DatabaseInitializationException - db fails to initialize |
| 77 | + * @throws IOException - failure when interacting with db. |
| 78 | + */ |
| 79 | + @BeforeAll |
| 80 | + static void dbSetup() throws DatabaseInitializationException, IOException { |
| 81 | + createDbContainer(); |
| 82 | + setDb(); |
| 83 | + migrateDb(); |
| 84 | + } |
| 85 | + |
| 86 | + /** |
| 87 | + * Close all resources (container, data source, dsl context, database). Should not be called |
| 88 | + * externally. It is not private because junit cannot access private methods. |
| 89 | + * |
| 90 | + * @throws Exception - exception while closing resources |
| 91 | + */ |
| 92 | + @AfterAll |
| 93 | + static void dbDown() throws Exception { |
| 94 | + dslContext.close(); |
| 95 | + DataSourceFactory.close(dataSource); |
| 96 | + container.close(); |
| 97 | + } |
| 98 | + |
| 99 | + /** |
| 100 | + * Truncates tables to reset them. Designed to be used in between tests. |
| 101 | + * |
| 102 | + * Note: NEW TABLES -- When a new table is added to the db, it will need to be added here. |
| 103 | + * |
| 104 | + * @throws SQLException - failure in truncate query. |
| 105 | + */ |
| 106 | + static void truncateAllTables() throws SQLException { |
| 107 | + database.query(ctx -> ctx |
| 108 | + .execute( |
| 109 | + """ |
| 110 | + TRUNCATE TABLE |
| 111 | + actor, |
| 112 | + actor_catalog, |
| 113 | + actor_catalog_fetch_event, |
| 114 | + actor_definition, |
| 115 | + actor_definition_workspace_grant, |
| 116 | + actor_oauth_parameter, |
| 117 | + connection, |
| 118 | + connection_operation, |
| 119 | + operation, |
| 120 | + state, |
| 121 | + stream_reset, |
| 122 | + workspace, |
| 123 | + workspace_service_account |
| 124 | + """)); |
| 125 | + } |
| 126 | + |
| 127 | + private static void createDbContainer() { |
| 128 | + container = new PostgreSQLContainer<>("postgres:13-alpine") |
| 129 | + .withDatabaseName("airbyte") |
| 130 | + .withUsername("docker") |
| 131 | + .withPassword("docker"); |
| 132 | + container.start(); |
| 133 | + } |
| 134 | + |
| 135 | + private static void setDb() { |
| 136 | + dataSource = DatabaseConnectionHelper.createDataSource(container); |
| 137 | + dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); |
| 138 | + database = new Database(dslContext); |
| 139 | + } |
| 140 | + |
| 141 | + private static void migrateDb() throws IOException, DatabaseInitializationException { |
| 142 | + final Flyway flyway = FlywayFactory.create( |
| 143 | + dataSource, |
| 144 | + StreamResetPersistenceTest.class.getName(), |
| 145 | + ConfigsDatabaseMigrator.DB_IDENTIFIER, |
| 146 | + ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); |
| 147 | + new ConfigsDatabaseTestProvider(dslContext, flyway).create(true); |
| 148 | + } |
| 149 | + |
| 150 | +} |
0 commit comments