feature/test-liquibase-migration-from-a-prod-dump (#152)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/152 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
		| @@ -3,7 +3,7 @@ | ||||
|  | ||||
| -- ============================================================================ | ||||
| -- NUMERIC-HASH-FUNCTIONS | ||||
| --changeset michael.hoennig:hash endDelimiter:--// | ||||
| --changeset michael.hoennig:hash runOnChange:true validCheckSum:ANY endDelimiter:--// | ||||
| -- ---------------------------------------------------------------------------- | ||||
|  | ||||
| do $$ | ||||
|   | ||||
| @@ -870,18 +870,23 @@ $$; | ||||
|  | ||||
|  | ||||
| -- ============================================================================ | ||||
| --changeset michael.hoennig:rbac-base-PGSQL-ROLES context:!external-db endDelimiter:--// | ||||
| --changeset michael.hoennig:rbac-base-PGSQL-ROLES runOnChange:true validCheckSum:ANY context:!external-db endDelimiter:--// | ||||
| -- ---------------------------------------------------------------------------- | ||||
|  | ||||
| do $$ | ||||
|     begin | ||||
|         if '${HSADMINNG_POSTGRES_ADMIN_USERNAME}'='admin' then | ||||
|             create role admin; | ||||
|             if not exists (select from pg_catalog.pg_roles where rolname = 'admin') then | ||||
|                 create role admin; | ||||
|             end if; | ||||
|             grant all privileges on all tables in schema public to admin; | ||||
|         end if; | ||||
|  | ||||
|         if '${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}'='restricted' then | ||||
|             create role restricted; | ||||
|             if not exists (select from pg_catalog.pg_roles where rolname = 'restricted') then | ||||
|                 create role restricted; | ||||
|             end if; | ||||
|  | ||||
|             grant all privileges on all tables in schema public to restricted; | ||||
|         end if; | ||||
|     end $$; | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import static net.hostsharing.hsadminng.config.HttpHeadersBuilder.headers; | ||||
| import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static com.github.tomakehurst.wiremock.client.WireMock.*; | ||||
|  | ||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) | ||||
| @TestPropertySource(properties = {"server.port=0", "hsadminng.cas.server=http://localhost:8088/cas"}) | ||||
| @ActiveProfiles("wiremock") // IMPORTANT: To test prod config, do not use test profile! | ||||
|   | ||||
| @@ -0,0 +1,136 @@ | ||||
| package net.hostsharing.hsadminng.hs.migration; | ||||
|  | ||||
| import liquibase.Liquibase; | ||||
| import liquibase.exception.LiquibaseException; | ||||
| import net.hostsharing.hsadminng.context.Context; | ||||
| import net.hostsharing.hsadminng.rbac.test.JpaAttempt; | ||||
| import org.junit.jupiter.api.BeforeEach; | ||||
| import org.junit.jupiter.api.Tag; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; | ||||
| import org.springframework.context.annotation.Import; | ||||
| import org.springframework.test.annotation.DirtiesContext; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| import org.springframework.test.context.jdbc.Sql; | ||||
|  | ||||
| import javax.sql.DataSource; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
|  | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; | ||||
|  | ||||
| // TODO.impl: The reference-SQL-dump-generation needs to be automated | ||||
| // BLOG: Liquibase-migration-test (not before the reference-SQL-dump-generation is simplified) | ||||
| // HOWTO: generate the prod-reference-SQL-dump during a prod-release | ||||
|  | ||||
| /** | ||||
|  * Tests, if the Liquibase scripts can be applied to a database ionitialized with schemas | ||||
|  * and test-data from a previous version. | ||||
|  * | ||||
|  * <p>The test needs a dump, ideally from the version of the lastest prod-release:</p> | ||||
|  * | ||||
|  * <ol> | ||||
|  * <li>clean the database:<br/> | ||||
|  * <code>pg-sql-reset</code> | ||||
|  * </li> | ||||
|  * | ||||
|  * <li>restote the database from latest dump</br> | ||||
|  *  <pre><code> | ||||
|  *      docker exec -i hsadmin-ng-postgres psql -U postgres postgres \ | ||||
|  *          <src/test/resources/db/prod-only-office-schema-with-test-data.sql | ||||
|  *  </code></pre> | ||||
|  * </li> | ||||
|  * | ||||
|  * <li>run the missing migrations:</br> | ||||
|  * <code>gw bootRun --args='--spring.profiles.active=only-office'</code> | ||||
|  * </li> | ||||
|  * | ||||
|  * <li>create the reference-schema SQL-file with some initializations:</li> | ||||
|  * <pre><code> | ||||
|  * cat >src/test/resources/db/prod-only-office-schema-with-test-data.sql <<EOF | ||||
|  * -- ================================================================================= | ||||
|  * -- Generated reference-SQL-dump (hopefully of latest prod-release). | ||||
|  * -- See: net.hostsharing.hsadminng.hs.migration.LiquibaseCompatibilityIntegrationTest | ||||
|  * -- --------------------------------------------------------------------------------- | ||||
|  * | ||||
|  * -- | ||||
|  * -- Explicit pre-initialization because we cannot use \`pg_dump --create ...\` | ||||
|  * -- because the database is already created by Testcontainers. | ||||
|  * -- | ||||
|  * | ||||
|  * CREATE ROLE postgres; | ||||
|  * | ||||
|  * CREATE ROLE admin; | ||||
|  * GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO admin; | ||||
|  * CREATE ROLE restricted; | ||||
|  * GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO restricted; | ||||
|  * | ||||
|  * EOF | ||||
|  * </code></pre> | ||||
|  * </li> | ||||
|  * | ||||
|  * <li>add the dump to that reference-schema SQL-file:</p> | ||||
|  * <pre><code>docker exec -i hsadmin-ng-postgres /usr/bin/pg_dump \ | ||||
|  * --column-inserts --disable-dollar-quoting -U postgres postgres \ | ||||
|  * >>src/test/resources/db/prod-only-office-schema-with-test-data.sql | ||||
|  * </code></pre> | ||||
|  * </li> | ||||
|  * </ol> | ||||
|  * | ||||
|  * <p>The generated dump has to be committed to git and will be used in future test-runs | ||||
|  * until it gets replaced at the next release.</p> | ||||
|  */ | ||||
| @Tag("officeIntegrationTest") | ||||
| @DataJpaTest(properties = { | ||||
|         "spring.liquibase.enabled=false" // @Sql should go first, Liquibase will be initialized programmatically | ||||
| }) | ||||
| @DirtiesContext | ||||
| @ActiveProfiles("liquibase-migration-test") | ||||
| @Import({ Context.class, JpaAttempt.class, LiquibaseConfig.class }) | ||||
| @Sql(value = "/db/prod-only-office-schema-with-test-data.sql", executionPhase = BEFORE_TEST_CLASS) | ||||
| public class LiquibaseCompatibilityIntegrationTest extends CsvDataImport { | ||||
|  | ||||
|     private static final String EXPECTED_CHANGESET_ONLY_AFTER_NEW_MIGRATION = "hs-hosting-SCHEMA"; | ||||
|     private static int initialChangeSetCount = 0; | ||||
|  | ||||
|     @Autowired | ||||
|     private DataSource dataSource; | ||||
|  | ||||
|     @Autowired | ||||
|     private Liquibase liquibase; | ||||
|  | ||||
|     @BeforeEach | ||||
|     public void setup() throws Exception { | ||||
|         assertThatDatabaseIsInitialized(); | ||||
|         runLiquibaseMigrations(); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void test() { | ||||
|         final var liquibaseScripts = singleColumnSqlQuery("SELECT id FROM public.databasechangelog"); | ||||
|         assertThat(liquibaseScripts).hasSizeGreaterThan(initialChangeSetCount); | ||||
|         assertThat(liquibaseScripts).contains(EXPECTED_CHANGESET_ONLY_AFTER_NEW_MIGRATION); | ||||
|     } | ||||
|  | ||||
|     private void assertThatDatabaseIsInitialized() { | ||||
|         final var schemas = singleColumnSqlQuery("SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname='public'"); | ||||
|         assertThat(schemas).containsExactly("databasechangelog", "databasechangeloglock"); | ||||
|  | ||||
|         final var liquibaseScripts = singleColumnSqlQuery("SELECT * FROM public.databasechangelog"); | ||||
|         assertThat(liquibaseScripts).hasSizeGreaterThan(285); | ||||
|         assertThat(liquibaseScripts).doesNotContain(EXPECTED_CHANGESET_ONLY_AFTER_NEW_MIGRATION); | ||||
|         initialChangeSetCount = liquibaseScripts.size(); | ||||
|     } | ||||
|  | ||||
|     private void runLiquibaseMigrations() throws LiquibaseException { | ||||
|         liquibase.update(new liquibase.Contexts(), new liquibase.LabelExpression()); | ||||
|     } | ||||
|  | ||||
|     private List<String> singleColumnSqlQuery(final String sql) { | ||||
|         //noinspection unchecked | ||||
|         final var rows = (List<Object>) em.createNativeQuery(sql).getResultList(); | ||||
|         return rows.stream().map(Objects::toString).toList(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,28 @@ | ||||
| package net.hostsharing.hsadminng.hs.migration; | ||||
|  | ||||
| import liquibase.Liquibase; | ||||
| import liquibase.database.DatabaseFactory; | ||||
| import liquibase.database.jvm.JdbcConnection; | ||||
| import liquibase.resource.ClassLoaderResourceAccessor; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.Profile; | ||||
|  | ||||
| import javax.sql.DataSource; | ||||
|  | ||||
| @Configuration | ||||
| @Profile("liquibase-migration-test") | ||||
| public class LiquibaseConfig { | ||||
|  | ||||
|     @Bean | ||||
|     public Liquibase liquibase(DataSource dataSource) throws Exception { | ||||
|         final var connection = dataSource.getConnection(); | ||||
|         final var database = DatabaseFactory.getInstance() | ||||
|                 .findCorrectDatabaseImplementation(new JdbcConnection(connection)); | ||||
|         return new Liquibase( | ||||
|                 "db/changelog/db.changelog-master.yaml", // Path to your Liquibase changelog | ||||
|                 new ClassLoaderResourceAccessor(), | ||||
|                 database | ||||
|         ); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17155
									
								
								src/test/resources/db/prod-only-office-schema-with-test-data.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17155
									
								
								src/test/resources/db/prod-only-office-schema-with-test-data.sql
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user