diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SystemManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SystemManagementTest.java index 2d182b288..5e46aa27d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SystemManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SystemManagementTest.java @@ -20,8 +20,10 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.report.model.TenantUsage; +import org.eclipse.hawkbit.repository.test.util.DisposableSqlTestDatabaseExtension; import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import io.qameta.allure.Description; import io.qameta.allure.Feature; @@ -29,6 +31,7 @@ import io.qameta.allure.Story; @Feature("Component Tests - Repository") @Story("System Management") +@ExtendWith(DisposableSqlTestDatabaseExtension.class) public class SystemManagementTest extends AbstractJpaIntegrationTest { @Test diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/tenancy/MultiTenancyEntityTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/tenancy/MultiTenancyEntityTest.java index 9b1452fb6..fe247c251 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/tenancy/MultiTenancyEntityTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/tenancy/MultiTenancyEntityTest.java @@ -19,9 +19,11 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.test.util.DisposableSqlTestDatabaseExtension; import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule; import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.data.domain.Slice; import io.qameta.allure.Description; @@ -36,6 +38,7 @@ import io.qameta.allure.Story; */ @Feature("Component Tests - Repository") @Story("Multi Tenancy") +@ExtendWith(DisposableSqlTestDatabaseExtension.class) public class MultiTenancyEntityTest extends AbstractJpaIntegrationTest { @Test diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java index df453f609..b00b01b62 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java @@ -90,7 +90,7 @@ import org.springframework.test.context.TestExecutionListeners.MergeMode; import org.springframework.test.context.TestPropertySource; @ActiveProfiles({ "test" }) -@ExtendWith({ JUnitTestLoggerExtension.class, WithSpringAuthorityRule.class }) +@ExtendWith({ JUnitTestLoggerExtension.class, WithSpringAuthorityRule.class , SharedSqlTestDatabaseExtension.class }) @WithUser(principal = "bumlux", allSpPermissions = true, authorities = { CONTROLLER_ROLE, SYSTEM_ROLE }) @SpringBootTest @ContextConfiguration(classes = { TestConfiguration.class, TestSupportBinderAutoConfiguration.class }) @@ -102,9 +102,8 @@ import org.springframework.test.context.TestPropertySource; // Cleaning repository will fire "delete" events. We won't count them to the // test execution. So, the order execution between EventVerifier and Cleanup is // important! -@TestExecutionListeners(listeners = { EventVerifier.class, CleanupTestExecutionListener.class, - MySqlTestDatabase.class, MsSqlTestDatabase.class, - PostgreSqlTestDatabase.class }, mergeMode = MergeMode.MERGE_WITH_DEFAULTS) +@TestExecutionListeners(listeners = { EventVerifier.class, CleanupTestExecutionListener.class }, + mergeMode = MergeMode.MERGE_WITH_DEFAULTS) @TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true") public abstract class AbstractIntegrationTest { private static final Logger LOG = LoggerFactory.getLogger(AbstractIntegrationTest.class); diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractSqlTestDatabase.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractSqlTestDatabase.java index e8bd992a6..194128323 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractSqlTestDatabase.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractSqlTestDatabase.java @@ -8,48 +8,47 @@ */ package org.eclipse.hawkbit.repository.test.util; +import static java.sql.DriverManager.getConnection; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener; +import org.springframework.util.AntPathMatcher; /** - * A {@link TestExecutionListener} for creating and dropping MySql schemas if - * tests are setup with MySql. + * A {@link TestExecutionListener} for creating and dropping SQL schemas if + * tests are setup with an SQL schema. */ public abstract class AbstractSqlTestDatabase extends AbstractTestExecutionListener { - private static final Logger LOG = LoggerFactory.getLogger(AbstractSqlTestDatabase.class); - protected String schemaName; - protected String uri; - protected String username; - protected String password; + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSqlTestDatabase.class); + protected static final AntPathMatcher MATCHER = new AntPathMatcher(); - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - if (isRunningWithSql()) { - LOG.info("Setting up database for test class {}", testContext.getTestClass().getName()); - this.username = System.getProperty("spring.datasource.username"); - this.password = System.getProperty("spring.datasource.password"); - this.uri = System.getProperty("spring.datasource.url"); - createSchemaUri(); - createSchema(); - } + protected final DatasourceContext context; + + public AbstractSqlTestDatabase(final DatasourceContext context) { + this.context = context; } - @Override - public void afterTestClass(final TestContext testContext) throws Exception { - if (isRunningWithSql()) { - dropSchema(); + protected abstract AbstractSqlTestDatabase createRandomSchema(); + + protected abstract void dropRandomSchema(); + + protected abstract String getRandomSchemaUri(); + + protected void executeStatement(final String uri, final String statement) { + LOGGER.trace("\033[0;33mExecuting statement {} on uri {} \033[0m", statement, uri); + + try (final Connection connection = getConnection(uri, context.getUsername(), context.getPassword()); + final PreparedStatement preparedStatement = connection.prepareStatement(statement)) { + preparedStatement.execute(); + } catch (final SQLException e) { + LOGGER.error("Execution of statement '{}' on uri {} failed!", statement, uri, e); } } - - protected abstract void createSchemaUri(); - - protected abstract boolean isRunningWithSql(); - - protected abstract void createSchema(); - - protected abstract void dropSchema(); } diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/DatasourceContext.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/DatasourceContext.java new file mode 100644 index 000000000..5e3987c51 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/DatasourceContext.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2022 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.test.util; + +import org.apache.commons.lang3.RandomStringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Holds all database related configuration + */ +public class DatasourceContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(DatasourceContext.class); + + public static final String SPRING_DATASOURCE_URL_KEY = "spring.datasource.url"; + public static final String SPRING_DATABASE_KEY = "spring.jpa.database"; + public static final String SPRING_DATABASE_USERNAME_KEY = "spring.datasource.username"; + public static final String SPRING_DATABASE_PASSWORD_KEY = "spring.datasource.password"; + public static final String DATABASE_PREFIX_KEY = "spring.database.random.prefix"; + private static final String RANDOM_DB_PREFIX = System.getProperty(DATABASE_PREFIX_KEY, "HAWKBIT_TEST_"); + + private final String database; + private final String datasourceUrl; + private final String username; + private final String password; + private final String randomSchemaName = RANDOM_DB_PREFIX + RandomStringUtils.randomAlphanumeric(10); + + /** + * Constructor + */ + public DatasourceContext(final String database, final String datasourceUrl, final String username, + final String password) { + this.database = database; + this.datasourceUrl = datasourceUrl; + this.username = username; + this.password = password; + } + + /** + * Constructor + */ + public DatasourceContext() { + database = System.getProperty(SPRING_DATABASE_KEY, System.getProperty(upperCaseVariant(SPRING_DATABASE_KEY))); + datasourceUrl = System.getProperty(SPRING_DATASOURCE_URL_KEY, + System.getProperty(upperCaseVariant(SPRING_DATASOURCE_URL_KEY))); + username = System.getProperty(SPRING_DATABASE_USERNAME_KEY, + System.getProperty(upperCaseVariant(SPRING_DATABASE_USERNAME_KEY))); + password = System.getProperty(SPRING_DATABASE_PASSWORD_KEY, + System.getProperty(upperCaseVariant(SPRING_DATABASE_PASSWORD_KEY))); + } + + private static String upperCaseVariant(final String key) { + return key.toUpperCase().replace('.', '_'); + } + + public String getDatabase() { + return database; + } + + public String getDatasourceUrl() { + return datasourceUrl; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getRandomSchemaName() { + return randomSchemaName; + } + + public boolean isNotProperlyConfigured() { + LOGGER.debug("Datasource environment variables: [database: {}, username: {}, password: {}, datasourceUrl: {}]", + database, username, password, datasourceUrl); + + return database == null || datasourceUrl == null || username == null || password == null; + } +} diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/DisposableSqlTestDatabaseExtension.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/DisposableSqlTestDatabaseExtension.java new file mode 100644 index 000000000..ffdf0bdb4 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/DisposableSqlTestDatabaseExtension.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2022 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.test.util; + + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.eclipse.hawkbit.repository.test.util.DatasourceContext.SPRING_DATASOURCE_URL_KEY; + +/** + * Provides a convenient way to generate a test database that can be used, and disposed of after the test is executed. + */ +public class DisposableSqlTestDatabaseExtension extends SharedSqlTestDatabaseExtension implements AfterAllCallback { + + private static final Logger LOGGER = LoggerFactory.getLogger(SharedSqlTestDatabaseExtension.class); + + private DatasourceContext datasourceContext = null; + + @Override + public void beforeAll(final ExtensionContext extensionContext) { + super.beforeAll(extensionContext); + final DatasourceContext sharedContext = CONTEXT.get(); + if (sharedContext == null || sharedContext.isNotProperlyConfigured()) { + return; + } + datasourceContext = new DatasourceContext(sharedContext.getDatabase(), sharedContext.getDatasourceUrl(), + sharedContext.getUsername(), sharedContext.getPassword()); + final AbstractSqlTestDatabase database = matchingDatabase(datasourceContext); + final String randomSchemaUri = database.createRandomSchema().getRandomSchemaUri(); + LOGGER.info("\033[0;33mRandom Schema URI is {} \033[0m", randomSchemaUri); + System.setProperty(SPRING_DATASOURCE_URL_KEY, randomSchemaUri); + } + + @Override + public void afterAll(final ExtensionContext extensionContext) { + if (datasourceContext == null) { + return; + } + matchingDatabase(datasourceContext).dropRandomSchema(); + System.setProperty(SPRING_DATASOURCE_URL_KEY, matchingDatabase(CONTEXT.get()).getRandomSchemaUri()); + } +} diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/H2TestDatabase.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/H2TestDatabase.java new file mode 100644 index 000000000..62a394d6b --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/H2TestDatabase.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2022 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.test.util; + +import org.junit.jupiter.api.extension.Extension; + +/** + * An {@link Extension} for creating and dropping H2 schemas if + * tests are set up with H2. + */ +public class H2TestDatabase extends AbstractSqlTestDatabase { + + public H2TestDatabase(final DatasourceContext context) { + super(context); + } + + @Override + public H2TestDatabase createRandomSchema() { + // do nothing, since H2 is in memory + return this; + } + + @Override + protected void dropRandomSchema() { + // do nothing, since H2 is in memory + } + + @Override + protected String getRandomSchemaUri() { + return "jdbc:h2:mem:" + context.getRandomSchemaName() +";MODE=MySQL;"; + } +} diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/JUnitTestLoggerExtension.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/JUnitTestLoggerExtension.java index 75dbbe561..39ac13811 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/JUnitTestLoggerExtension.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/JUnitTestLoggerExtension.java @@ -19,17 +19,17 @@ public class JUnitTestLoggerExtension implements BeforeTestExecutionCallback, Te private static final Logger LOG = LoggerFactory.getLogger(JUnitTestLoggerExtension.class); @Override - public void testSuccessful(ExtensionContext context) { + public void testSuccessful(final ExtensionContext context) { LOG.info("Test {} succeeded.", context.getTestMethod()); } @Override - public void testFailed(ExtensionContext context, Throwable cause) { - LOG.error("Test {} failed with {}.", context.getTestMethod()); + public void testFailed(final ExtensionContext context, final Throwable cause) { + LOG.error("Test {} failed with {}.", context.getTestMethod(), cause.getMessage()); } @Override - public void beforeTestExecution(ExtensionContext context) throws Exception { + public void beforeTestExecution(final ExtensionContext context) { LOG.info("Starting Test {}...", context.getTestMethod()); } } diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/MsSqlTestDatabase.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/MsSqlTestDatabase.java index dd4bbdd6e..4a8183310 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/MsSqlTestDatabase.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/MsSqlTestDatabase.java @@ -8,67 +8,45 @@ */ package org.eclipse.hawkbit.repository.test.util; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.extension.Extension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.test.context.TestExecutionListener; /** - * A {@link TestExecutionListener} for creating and dropping MS SQL Server - * schemas if tests are setup with MS SQL Server. + * An {@link Extension} for creating and dropping MS SQL Server + * schemas if tests are set up with MS SQL Server. */ public class MsSqlTestDatabase extends AbstractSqlTestDatabase { - private static final Logger LOG = LoggerFactory.getLogger(MsSqlTestDatabase.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MsSqlTestDatabase.class); - @Override - protected void createSchemaUri() { - schemaName = "SP" + RandomStringUtils.randomAlphanumeric(10); - this.uri = this.uri.substring(0, uri.indexOf(';')); - - System.setProperty("spring.datasource.url", uri + ";database=" + schemaName); + public MsSqlTestDatabase(final DatasourceContext context) { + super(context); } @Override - protected boolean isRunningWithSql() { - return "SQL_SERVER".equals(System.getProperty("spring.jpa.database")); + public MsSqlTestDatabase createRandomSchema() { + final String uri = context.getDatasourceUrl(); + LOGGER.info("\033[0;33mCreating mssql schema {} \033[0m", context.getRandomSchemaName()); + + executeStatement(uri.split(";database=")[0], "CREATE DATABASE " + context.getRandomSchemaName() + ";"); + return this; } @Override - protected void createSchema() { - try (Connection connection = DriverManager.getConnection(uri, username, password)) { - try (PreparedStatement statement = connection.prepareStatement("CREATE DATABASE " + schemaName + ";")) { - LOG.info("Creating schema {} on uri {}", schemaName, uri); - statement.execute(); - LOG.info("Created schema {} on uri {}", schemaName, uri); - } - } catch (final SQLException e) { - LOG.error("Schema creation failed!", e); - } + protected void dropRandomSchema() { + final String uri = context.getDatasourceUrl(); + final String dbServerUri = uri.split(";database=")[0]; + LOGGER.info("\033[0;33mDropping mssql schema {} \033[0m", context.getRandomSchemaName()); + // Needed to avoid the DROP is rejected with "database still in use" + executeStatement(dbServerUri, "ALTER DATABASE " + context.getRandomSchemaName() + " SET SINGLE_USER WITH ROLLBACK IMMEDIATE;"); + executeStatement(dbServerUri, "DROP DATABASE " + context.getRandomSchemaName() + ";"); } @Override - protected void dropSchema() { - try (Connection connection = DriverManager.getConnection(uri, username, password)) { - // Needed to avoid the DROP is rejected with "database still in use" - try (PreparedStatement statement = connection - .prepareStatement("ALTER DATABASE " + schemaName + " SET SINGLE_USER WITH ROLLBACK IMMEDIATE;")) { - statement.execute(); - } - - try (PreparedStatement statement = connection.prepareStatement("DROP DATABASE " + schemaName + ";")) { - LOG.info("Dropping schema {} on uri {}", schemaName, uri); - statement.execute(); - LOG.info("Dropped schema {} on uri {}", schemaName, uri); - } - } catch (final SQLException e) { - LOG.error("Schema drop failed!", e); - } + protected String getRandomSchemaUri() { + final String uri = context.getDatasourceUrl(); + return uri.substring(0, uri.indexOf(';')) + ";database=" + context.getRandomSchemaName(); } } diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/MySqlTestDatabase.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/MySqlTestDatabase.java index 6ebdc0337..862f5553a 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/MySqlTestDatabase.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/MySqlTestDatabase.java @@ -8,61 +8,55 @@ */ package org.eclipse.hawkbit.repository.test.util; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.util.Map; -import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.extension.Extension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.test.context.TestExecutionListener; /** - * A {@link TestExecutionListener} for creating and dropping MySql schemas if - * tests are setup with MySql. + * An {@link Extension} for creating and dropping MySql schemas if + * tests are set up with MySql. */ public class MySqlTestDatabase extends AbstractSqlTestDatabase { - private static final Logger LOG = LoggerFactory.getLogger(MySqlTestDatabase.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MySqlTestDatabase.class); + protected static final String MYSQL_URI_PATTERN = "jdbc:mysql://{host}:{port}/{db}*"; - @Override - protected void createSchemaUri() { - schemaName = "SP" + RandomStringUtils.randomAlphanumeric(10); - this.uri = this.uri.substring(0, uri.lastIndexOf('/') + 1); - - System.setProperty("spring.datasource.url", uri + schemaName); + public MySqlTestDatabase(final DatasourceContext context) { + super(context); } @Override - protected boolean isRunningWithSql() { - return "MYSQL".equals(System.getProperty("spring.jpa.database")); + public MySqlTestDatabase createRandomSchema() { + final String uri = context.getDatasourceUrl(); + final String schemaName = getSchemaName(uri); + LOGGER.info("\033[0;33mCreating mysql schema {} if not existing \033[0m", context.getRandomSchemaName()); + + executeStatement(uri.split("/" + schemaName)[0], + "CREATE SCHEMA IF NOT EXISTS " + context.getRandomSchemaName() + ";"); + return this; } @Override - protected void createSchema() { - try (Connection connection = DriverManager.getConnection(uri, username, password)) { - try (PreparedStatement statement = connection.prepareStatement("CREATE SCHEMA " + schemaName + ";")) { - LOG.info("Creating schema {} on uri {}", schemaName, uri); - statement.execute(); - LOG.info("Created schema {} on uri {}", schemaName, uri); - } - } catch (final SQLException e) { - LOG.error("Schema creation failed!", e); - } - + protected void dropRandomSchema() { + final String uri = context.getDatasourceUrl(); + final String schemaName = getSchemaName(uri); + LOGGER.info("\033[0;33mDropping mysql schema {} \033[0m", context.getRandomSchemaName()); + executeStatement(uri.split("/" + schemaName)[0], "DROP SCHEMA " + context.getRandomSchemaName() + ";"); } @Override - protected void dropSchema() { - try (Connection connection = DriverManager.getConnection(uri, username, password)) { - try (PreparedStatement statement = connection.prepareStatement("DROP SCHEMA " + schemaName + ";")) { - LOG.info("Dropping schema {} on uri {}", schemaName, uri); - statement.execute(); - LOG.info("Dropped schema {} on uri {}", schemaName, uri); - } - } catch (final SQLException e) { - LOG.error("Schema drop failed!", e); - } + protected String getRandomSchemaUri() { + final String uri = context.getDatasourceUrl(); + final Map databaseProperties = MATCHER.extractUriTemplateVariables(MYSQL_URI_PATTERN, uri); + + return MYSQL_URI_PATTERN.replace("{host}", databaseProperties.get("host")) + .replace("{port}", databaseProperties.get("port")) + .replace("{db}*", context.getRandomSchemaName()); + } + + private static String getSchemaName(final String datasourceUrl) { + return MATCHER.extractUriTemplateVariables(MYSQL_URI_PATTERN, datasourceUrl).get("db"); } } diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/PostgreSqlTestDatabase.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/PostgreSqlTestDatabase.java index 5789aad3a..8e69cea1d 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/PostgreSqlTestDatabase.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/PostgreSqlTestDatabase.java @@ -8,61 +8,57 @@ */ package org.eclipse.hawkbit.repository.test.util; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.util.Map; -import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.extension.Extension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.test.context.TestExecutionListener; /** - * A {@link TestExecutionListener} for creating and dropping MySql schemas if - * tests are setup with MySql. + * An {@link Extension} for creating and dropping MySql schemas if + * tests are set up with MySql. */ public class PostgreSqlTestDatabase extends AbstractSqlTestDatabase { - private static final Logger LOG = LoggerFactory.getLogger(PostgreSqlTestDatabase.class); + private static final Logger LOGGER = LoggerFactory.getLogger(PostgreSqlTestDatabase.class); + private static final String POSTGRESQL_URI_PATTERN = "jdbc:postgresql://{host}:{port}/{db}*"; - @Override - protected void createSchemaUri() { - schemaName = "sp" + RandomStringUtils.randomAlphanumeric(10).toLowerCase(); - this.uri = this.uri.substring(0, uri.indexOf('?')); - - System.setProperty("spring.datasource.url", uri + "?currentSchema=" + schemaName); + public PostgreSqlTestDatabase(final DatasourceContext context) { + super(context); } @Override - protected boolean isRunningWithSql() { - return "POSTGRESQL".equals(System.getProperty("spring.jpa.database")); + protected PostgreSqlTestDatabase createRandomSchema() { + LOGGER.info("\033[0;33mCreating postgreSql schema {} \033[0m", context.getRandomSchemaName()); + final String uri = getBaseUri() + "?currentSchema=" + getSchemaName(); + executeStatement(uri, "CREATE SCHEMA " + context.getRandomSchemaName() + ";"); + return this; } @Override - protected void createSchema() { - try (Connection connection = DriverManager.getConnection(uri, username, password)) { - try (PreparedStatement statement = connection.prepareStatement("CREATE schema " + schemaName + ";")) { - LOG.info("Creating schema {} on uri {}", schemaName, uri); - statement.execute(); - LOG.info("Created schema {} on uri {}", schemaName, uri); - } - } catch (final SQLException e) { - LOG.error("Schema creation failed!", e); - } - + protected void dropRandomSchema() { + LOGGER.info("\033[0;33mDropping postgreSql schema {}\033[0m", context.getRandomSchemaName()); + final String uri = getBaseUri() + "?currentSchema=" + getSchemaName(); + executeStatement(uri, "DROP SCHEMA " + context.getRandomSchemaName() + " CASCADE;"); } @Override - protected void dropSchema() { - try (Connection connection = DriverManager.getConnection(uri, username, password)) { - try (PreparedStatement statement = connection.prepareStatement("DROP schema " + schemaName + " CASCADE;")) { - LOG.info("Dropping schema {} on uri {}", schemaName, uri); - statement.execute(); - LOG.info("Dropped schema {} on uri {}", schemaName, uri); - } - } catch (final SQLException e) { - LOG.error("Schema drop failed!", e); - } + protected String getRandomSchemaUri() { + return getBaseUri() + "?currentSchema=" + context.getRandomSchemaName(); + } + + private String getBaseUri() { + final String uri = context.getDatasourceUrl(); + final Map databaseProperties = MATCHER.extractUriTemplateVariables(POSTGRESQL_URI_PATTERN, uri); + + return POSTGRESQL_URI_PATTERN.replace("{host}", databaseProperties.get("host")) + .replace("{port}", databaseProperties.get("port")) + .replace("{db}*", getSchemaName()); + } + + private String getSchemaName() { + return MATCHER.extractUriTemplateVariables(POSTGRESQL_URI_PATTERN, context.getDatasourceUrl()) + .get("db") + .split("\\?")[0]; } } diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/SharedSqlTestDatabaseExtension.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/SharedSqlTestDatabaseExtension.java new file mode 100644 index 000000000..98bbe5d4f --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/SharedSqlTestDatabaseExtension.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2022 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.test.util; + +import static org.eclipse.hawkbit.repository.test.util.DatasourceContext.SPRING_DATASOURCE_URL_KEY; + +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents a test database configuration for a "shared" database instance across all tests annotated with this extension + */ +public class SharedSqlTestDatabaseExtension implements BeforeAllCallback { + + private static final Logger LOGGER = LoggerFactory.getLogger(SharedSqlTestDatabaseExtension.class); + protected static final AtomicReference CONTEXT = new AtomicReference<>(); + + @Override + public void beforeAll(final ExtensionContext extensionContext) { + final DatasourceContext testDatasourceContext = new DatasourceContext(); + + if (testDatasourceContext.isNotProperlyConfigured()) { + LOGGER.info("\033[0;33mSchema generation skipped... No datasource environment variables found!\033[0m"); + return; + } + + // update CONTEXT only if the current value is null => initialize only + if (!CONTEXT.compareAndSet(null, testDatasourceContext)) { + final String randomSchemaUri = matchingDatabase(testDatasourceContext).getRandomSchemaUri(); + LOGGER.info("\033[0;33mReusing Random Schema at URI {} \033[0m", randomSchemaUri); + return; + } + + final AbstractSqlTestDatabase database = matchingDatabase(testDatasourceContext); + final String randomSchemaUri = database.createRandomSchema().getRandomSchemaUri(); + LOGGER.info("\033[0;33mRandom Schema URI is {} \033[0m", randomSchemaUri); + System.setProperty(SPRING_DATASOURCE_URL_KEY, randomSchemaUri); + + registerDropSchemaOnSystemShutdownHook(database, randomSchemaUri); + } + + private void registerDropSchemaOnSystemShutdownHook(final AbstractSqlTestDatabase database, final String schemaUri) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + LOGGER.warn("\033[0;33mDropping schema at url {} \033[0m", schemaUri); + database.dropRandomSchema(); + })); + } + + protected AbstractSqlTestDatabase matchingDatabase(final DatasourceContext context) { + AbstractSqlTestDatabase database; + + switch (context.getDatabase()) { + case "H2": + database = new H2TestDatabase(context); + break; + case "SQL_SERVER": + database = new MsSqlTestDatabase(context); + break; + case "MYSQL": + database = new MySqlTestDatabase(context); + break; + case "POSTGRESQL": + database = new PostgreSqlTestDatabase(context); + break; + default: + throw new IllegalStateException("No supported database found for type " + context.getDatabase()); + } + + return database; + } + +} diff --git a/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AbstractSecurityTest.java b/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AbstractSecurityTest.java index d70086c5e..f28d301fc 100644 --- a/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AbstractSecurityTest.java +++ b/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/AbstractSecurityTest.java @@ -8,24 +8,19 @@ */ package org.eclipse.hawkbit.app; -import org.eclipse.hawkbit.repository.test.util.MsSqlTestDatabase; -import org.eclipse.hawkbit.repository.test.util.MySqlTestDatabase; +import org.eclipse.hawkbit.repository.test.util.SharedSqlTestDatabaseExtension; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.TestExecutionListeners.MergeMode; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @SpringBootTest(properties = { "hawkbit.dmf.rabbitmq.enabled=false" }) -@TestExecutionListeners(listeners = { MySqlTestDatabase.class, - MsSqlTestDatabase.class }, mergeMode = MergeMode.MERGE_WITH_DEFAULTS) -@DirtiesContext +@ExtendWith(SharedSqlTestDatabaseExtension.class) public abstract class AbstractSecurityTest { @Autowired @@ -40,4 +35,4 @@ public abstract class AbstractSecurityTest { mvc = builder.build(); } -} \ No newline at end of file +} diff --git a/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java b/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java index 062a2ae6c..76d9f9d84 100644 --- a/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java +++ b/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java @@ -14,14 +14,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; -import org.eclipse.hawkbit.repository.test.util.MsSqlTestDatabase; -import org.eclipse.hawkbit.repository.test.util.MySqlTestDatabase; + import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpHeaders; import org.springframework.security.test.context.support.WithUserDetails; -import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.TestExecutionListeners.MergeMode; import org.springframework.test.web.servlet.ResultActions; import io.qameta.allure.Description; @@ -31,10 +28,7 @@ import io.qameta.allure.Story; @SpringBootTest(properties = { "hawkbit.dmf.rabbitmq.enabled=false", "hawkbit.server.security.cors.enabled=true", "hawkbit.server.security.cors.allowedOrigins=" + CorsTest.ALLOWED_ORIGIN_FIRST + "," + CorsTest.ALLOWED_ORIGIN_SECOND, - "hawkbit.server.security.cors.exposedHeaders=Access-Control-Allow-Origin" }) -@TestExecutionListeners(listeners = { MySqlTestDatabase.class, - MsSqlTestDatabase.class }, mergeMode = MergeMode.MERGE_WITH_DEFAULTS) -@Feature("Integration Test - Security") + "hawkbit.server.security.cors.exposedHeaders=Access-Control-Allow-Origin" })@Feature("Integration Test - Security") @Story("CORS") public class CorsTest extends AbstractSecurityTest {