diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml new file mode 100644 index 000000000..7fc37fe86 --- /dev/null +++ b/ebean-test/pom.xml @@ -0,0 +1,128 @@ + + + + ebean-parent + io.ebean + 12.4.3-SNAPSHOT + + 4.0.0 + + ebean-test + + + + + org.slf4j + slf4j-api + 1.7.25 + provided + + + + io.ebean + ebean + ${project.version} + provided + + + + io.ebean.test + ebean-test-docker + 3.1.5 + + + + org.assertj + assertj-core + 3.14.0 + + + + + org.glassfish.jaxb + jaxb-runtime + 2.3.2 + + + + + com.h2database + h2 + 1.4.200 + + + + org.postgresql + postgresql + 42.2.12 + test + + + + + + com.microsoft.sqlserver + mssql-jdbc + 6.2.2.jre8 + test + + + + mysql + mysql-connector-java + 8.0.16 + test + + + + org.mariadb.jdbc + mariadb-java-client + 2.6.0 + test + + + + com.sap.cloud.db.jdbc + ngdbc + 2.3.48 + test + + + + ch.qos.logback + logback-classic + 1.2.3 + test + + + + junit + junit + 4.12 + test + + + + + + + + + + io.repaint.maven + tiles-maven-plugin + 2.17 + true + + + io.ebean.tile:enhancement:12.4.2 + + + + + + + + diff --git a/ebean-test/src/main/java/io/ebean/test/CaptureLogger.java b/ebean-test/src/main/java/io/ebean/test/CaptureLogger.java new file mode 100644 index 000000000..1cdad509c --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/CaptureLogger.java @@ -0,0 +1,64 @@ +package io.ebean.test; + +import io.ebeaninternal.api.SpiLogger; + +import java.util.ArrayList; +import java.util.List; + +/** + * Capture the log messages (executed SQL) for testing. + */ +class CaptureLogger implements SpiLogger { + + private final SpiLogger wrapped; + + private List messages = new ArrayList<>(); + + private boolean active; + + CaptureLogger(SpiLogger wrapped) { + this.wrapped = wrapped; + } + + @Override + public boolean isDebug() { + return true; + } + + @Override + public boolean isTrace() { + return true; + } + + @Override + public void debug(String msg) { + if (active) { + messages.add(msg); + } + wrapped.debug(msg); + } + + @Override + public void trace(String msg) { + if (active) { + messages.add(msg); + } + wrapped.trace(msg); + } + + List start() { + this.active = true; + return collect(); + } + + List stop() { + this.active = false; + return collect(); + } + + List collect() { + List response = messages; + messages = new ArrayList<>(); + return response; + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/CapturingLoggerFactory.java b/ebean-test/src/main/java/io/ebean/test/CapturingLoggerFactory.java new file mode 100644 index 000000000..c30e25790 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/CapturingLoggerFactory.java @@ -0,0 +1,25 @@ +package io.ebean.test; + +import io.ebeaninternal.api.SpiLogger; +import io.ebeaninternal.api.SpiLoggerFactory; +import io.ebeaninternal.server.logger.DSpiLogger; +import org.slf4j.LoggerFactory; + +/** + * Create a logger that captures the SQL and register it for later access in tests. + *

+ * The logged SQL is accessed by LoggedSql. + *

+ */ +public class CapturingLoggerFactory implements SpiLoggerFactory { + + @Override + public SpiLogger create(String name) { + + DSpiLogger logger = new DSpiLogger(LoggerFactory.getLogger(name)); + if (name.equals("io.ebean.SQL")) { + return LoggedSql.register(logger); + } + return logger; + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/DbJson.java b/ebean-test/src/main/java/io/ebean/test/DbJson.java new file mode 100644 index 000000000..b7fae39bb --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/DbJson.java @@ -0,0 +1,110 @@ +package io.ebean.test; + +import io.ebean.DB; +import io.ebean.migration.util.IOUtils; + +import java.io.IOException; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Helper for testing to assert that the JSON form of an entity + * or list of entities match a String / typically test resource. + * + *
{@code
+ *
+ *    DbJson.of(timedEntries)
+ *      .replace("id", "eventTime")
+ *      .assertContentMatches("/assertJson/full-1-timed.json");
+ *
+ * }
+ */ +public class DbJson { + + /** + * Create a PrettyJson object that has the JSON form of the + * entity bean or beans. + * + *
{@code
+   *
+   *    DbJson.of(timedEntries)
+   *      .replace("id", "eventTime")
+   *      .assertContentMatches("/assertJson/full-1-timed.json");
+   *
+   * }
+ */ + public static PrettyJson of(Object bean) { + return new PrettyJson(DB.json().toJsonPretty(bean)); + } + + /** + * Read the content for the given resource path. + */ + public static String readResource(String resourcePath) { + InputStream is = DbJson.class.getResourceAsStream(resourcePath); + try { + return IOUtils.readUtf8(is).trim(); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Contains the JSON of beans(s). + */ + public static class PrettyJson { + + private String placeHolder = "\"*\""; + private String rawJson; + + PrettyJson(String rawJson) { + this.rawJson = rawJson; + } + + /** + * Set the placeHolder to use when replacing property values. + */ + public PrettyJson withPlaceholder(String placeHolder) { + this.placeHolder = placeHolder; + return this; + } + + /** + * Replace the values of the given properties with a placeholder value. + *

+ * Typically we do this on generated properties such as id and timestamp properties. + *

+ */ + public PrettyJson replace(String... propertyNames) { + for (String propertyName : propertyNames) { + String placeholder = "\"" + propertyName + "\": " + placeHolder; + rawJson = rawJson.replaceAll("\"" + propertyName + "\": (\\d+)", placeholder); + rawJson = rawJson.replaceAll("\"" + propertyName + "\": \"(.*?)\"", placeholder); + } + return this; + } + + /** + * Return the JSON content. + */ + public String asJson() { + return rawJson; + } + + /** + * Assert the json matches the content at the given resource path. + * + *
{@code
+     *
+     *    DbJson.of(timedEntries)
+     *      .replace("id", "eventTime")
+     *      .assertContentMatches("/assertJson/full-1-timed.json");
+     *
+     * }
+ */ + public void assertContentMatches(String resourcePath) { + assertThat(rawJson).isEqualTo(readResource(resourcePath)); + } + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/ForTests.java b/ebean-test/src/main/java/io/ebean/test/ForTests.java new file mode 100644 index 000000000..2bf7a9fb5 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/ForTests.java @@ -0,0 +1,115 @@ +package io.ebean.test; + +import io.ebean.DB; +import io.ebean.Transaction; +import io.ebeaninternal.api.HelpScopeTrans; + +/** + * Helper methods for testing. + */ +public class ForTests { + + /** + * Enable or disable @Transactional methods. + *

+ * This is intended for testing purposes such that tests + * on code with {@code @Transactional} methods don't actually + * start or complete transactions. + *

+ * + * @param enable Set false to disable {@code @Transactional} methods + */ + public static void enableTransactional(boolean enable) { + HelpScopeTrans.setEnabled(enable); + } + + /** + * Run the closure with @Transactional methods + * effectively disabled (they won't create/commit transactions). + */ + public static void noTransactional(Runnable run) { + try { + enableTransactional(false); + run.run(); + } finally { + enableTransactional(true); + } + } + + /** + * All transactions started in the closure are effectively rolled back. + *

+ * This creates a wrapping transaction that uses {@link Transaction#setNestedUseSavepoint()}. + * All nested transactions are created as savepoints. On completion the wrapping + * transaction is rolled back. + *

+ * + * @param run Closure that runs such that all the transactions are rolled back. + */ + public static void rollbackAll(Runnable run) { + + try (Transaction transaction = DB.beginTransaction()) { + transaction.setNestedUseSavepoint(); + run.run(); + + transaction.rollback(); + } + } + + /** + * Create and return a RollbackAll which should be closed at the end of the test(s). + *

+ * In tests for @Before we create the rollbackAll and on + * @After we close() it effectively rolling + * back all changes made during test execution. + *

+ * + *
{@code
+   *
+   *   private ForTests.RollbackAll rollbackAll;
+   *
+   *   @Before
+   *   public void before() {
+   *     rollbackAll = ForTests.createRollbackAll();
+   *   }
+   *
+   *   @After
+   *   public void after() {
+   *     rollbackAll.close();
+   *   }
+   *
+   *   ... tests execute and everything is rolled back
+   *
+   *
+   * }
+ */ + public static RollbackAll createRollbackAll() { + + final Transaction transaction = DB.beginTransaction(); + transaction.setNestedUseSavepoint(); + return new RollbackAll(transaction); + } + + /** + * A wrapping transaction used in test code to rollback all changes. + *

+ * We must ensure that close() is called. + *

+ */ + public static class RollbackAll implements AutoCloseable { + + private final Transaction transaction; + + private RollbackAll(Transaction transaction) { + this.transaction = transaction; + } + + /** + * Rollback the wrapping transaction. + */ + @Override + public void close() { + transaction.rollback(); + } + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/LoggedSql.java b/ebean-test/src/main/java/io/ebean/test/LoggedSql.java new file mode 100644 index 000000000..624451f12 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/LoggedSql.java @@ -0,0 +1,79 @@ +package io.ebean.test; + +import io.ebeaninternal.api.SpiLogger; +import io.ebeaninternal.server.logger.DSpiLogger; + +import java.util.List; + +/** + * Provides access to the messages logged to io.ebean.SQL. + *

+ * This is here to allow easy access to the SQL that was executed during testing and + * if desired we can use that to perform asserts in tests. + *

+ *
{@code
+ *
+ *     // start capturing SQL log messages
+ *     LoggedSql.start();
+ *
+ *     List customers =
+ *           Customer.find.where()
+ *             .name.ilike("rob%")
+ *             .findList();
+ *
+ *       assertNotNull(customers);
+ *
+ *     // perform an insert
+ *     new Product("ad", "asd").save()
+ *
+ *
+ *     // return the captured SQL log messages
+ *     // since LoggedSql.start()
+ *     List sql = LoggedSql.stop();
+ *
+ *     assertThat(sql).hasSize(2);
+ *     assertThat(sql.get(0)).contains("from customer");
+ *     assertThat(sql.get(1)).contains("into product");
+ *
+ * }
+ */ +public class LoggedSql { + + private static CaptureLogger sqlLogger; + + /** + * Internal use - register the logger for io.ebean.SQL. + */ + static SpiLogger register(DSpiLogger logger) { + if (sqlLogger == null) { + sqlLogger = new CaptureLogger(logger); + } + return sqlLogger; + } + + /** + * Start the capture of the io.ebean.SQL messages. + */ + public static List start() { + return sqlLogger.start(); + } + + /** + * Stop the capture of the io.ebean.SQL messages and return the messages/sql + * that was captured since the call to start(). + */ + public static List stop() { + return sqlLogger.stop(); + } + + /** + * Collect and return the messages/sql that was captured since the call to start() or collect(). + *

+ * Unlike stop() collection of messages will continue. + *

+ */ + public static List collect() { + return sqlLogger.collect(); + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/UserContext.java b/ebean-test/src/main/java/io/ebean/test/UserContext.java new file mode 100644 index 000000000..a133d07b7 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/UserContext.java @@ -0,0 +1,85 @@ +package io.ebean.test; + +/** + * Use in test code when the CurrentUserProvider and/or CurrentTenantProvider were configured by + * this ebean-test-config plugin. + *

+ * That is, the ebean-test-config plugin will check if there ia a CurrentUserProvider and if not + * automatically set one and that provider reads the 'current user' from this UserContext. + *

{@code
+ *
+ *   // set the current userId which will be put
+ *   // into 'WhoCreated' and 'WhoModified' properties
+ *
+ * 	 UserContext.setUserId("U1");
+ *
+ * 	 // persist bean that has ... a 'WhoModified' property
+ *   Content content = new Content();
+ *   content.setName("hello");
+ *
+ *   content.save();
+ *
+ * }
+ */ +public class UserContext { + + private static final UserContextThreadLocal local = new UserContextThreadLocal(); + + private Object userId; + private Object tenantId; + + private UserContext() { + } + + /** + * Return the current user. + */ + public static Object currentUserId() { + return local.get().userId; + } + + /** + * Return the current tenantId. + */ + public static Object currentTenantId() { + return local.get().tenantId; + } + + /** + * Set the current userId - this value is put into 'WhoCreated' and 'WhoModified' properties. + */ + public static void setUserId(Object userId) { + local.get().userId = userId; + } + + /** + * Set the current tenantId. + */ + public static void setTenantId(Object tenantId) { + local.get().tenantId = tenantId; + } + + /** + * Clear both the current userId and tenantId. + */ + public static void reset() { + local.remove(); + } + + /** + * Set both the current userId and current tenantId. + */ + public static void set(Object userId, String tenantId) { + UserContext userContext = local.get(); + userContext.userId = userId; + userContext.tenantId = tenantId; + } + + private static class UserContextThreadLocal extends ThreadLocal { + + @Override + protected UserContext initialValue() { + return new UserContext(); + } + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/AutoConfigureForTesting.java b/ebean-test/src/main/java/io/ebean/test/config/AutoConfigureForTesting.java new file mode 100644 index 000000000..d56f4f7c5 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/AutoConfigureForTesting.java @@ -0,0 +1,88 @@ +package io.ebean.test.config; + +import io.ebean.config.AutoConfigure; +import io.ebean.config.DatabaseConfig; +import io.ebean.datasource.DataSourceConfig; +import io.ebean.test.config.platform.PlatformAutoConfig; +import io.ebean.test.config.provider.ProviderAutoConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Properties; + +/** + * Automatically configure ServerConfig for testing purposes. + *

+ * Can setup and execute docker based databases and other containers. + * Can setup DataSource configuration (to match docker db setup). + * Can setup a CurrentUserProvider and CurrentTenantProvider for testing. + * Can setup a EncryptKeyManager for testing purposes with fixed key. + */ +public class AutoConfigureForTesting implements AutoConfigure { + + private static final Logger log = LoggerFactory.getLogger(AutoConfigureForTesting.class); + + /** + * System property that can override the platform. mvn clean test -Ddb=sqlserver + */ + private final String environmentDb = System.getProperty("db"); + + @Override + public void preConfigure(DatabaseConfig config) { + + Properties properties = config.getProperties(); + if (isExtraServer(config, properties)) { + setupExtraDataSourceIfNecessary(config); + return; + } + + String testPlatform = properties.getProperty("ebean.test.platform"); + log.debug("automatic testing config - with ebean.test.platform:{} environment db:{} name:{}", testPlatform, environmentDb, config.getName()); + + if (RunOnceMarker.isRun()) { + setupPlatform(environmentDb, config); + } + } + + @Override + public void postConfigure(DatabaseConfig config) { + setupProviders(config); + } + + /** + * Check if this is not the primary server and return true if that is the case. + */ + private boolean isExtraServer(DatabaseConfig config, Properties properties) { + String extraDb = properties.getProperty("ebean.test.extraDb.dbName", properties.getProperty("ebean.test.extraDb")); + if (extraDb != null && extraDb.equals(config.getName())) { + config.setDefaultServer(false); + return true; + } + return false; + } + + /** + * Setup the DataSource on the extra database if necessary. + */ + private void setupExtraDataSourceIfNecessary(DatabaseConfig config) { + DataSourceConfig dataSourceConfig = config.getDataSourceConfig(); + if (dataSourceConfig == null || dataSourceConfig.getUsername() == null) { + new PlatformAutoConfig(environmentDb, config) + .configExtraDataSource(); + } + } + + /** + * Setup support for Who, Multi-Tenant and DB encryption if they are not already set. + */ + private void setupProviders(DatabaseConfig config) { + new ProviderAutoConfig(config).run(); + } + + /** + * Setup the platform for testing including docker as needed and adjusting datasource config as needed. + */ + private void setupPlatform(String db, DatabaseConfig config) { + new PlatformAutoConfig(db, config).run(); + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/RunOnceMarker.java b/ebean-test/src/main/java/io/ebean/test/config/RunOnceMarker.java new file mode 100644 index 000000000..8a9193fa0 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/RunOnceMarker.java @@ -0,0 +1,16 @@ +package io.ebean.test.config; + +import io.ebeaninternal.dbmigration.DbOffline; + +class RunOnceMarker { + + private static boolean hasRun; + + static synchronized boolean isRun() { + if (DbOffline.isSet() || hasRun) { + return false; + } + hasRun = true; + return true; + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/ClickHouseSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/ClickHouseSetup.java new file mode 100644 index 000000000..4dcb472b4 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/ClickHouseSetup.java @@ -0,0 +1,41 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class ClickHouseSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + config.ddlMode("dropCreate"); + config.setDefaultPort(8123); + config.setUsername("default"); + config.setPassword(""); + config.setUrl("jdbc:clickhouse://localhost:${port}/${databaseName}"); + config.setDriver("ru.yandex.clickhouse.ClickHouseDriver"); + config.datasourceDefaults(); + + return dockerProperties(config); + } + + private Properties dockerProperties(Config dbConfig) { + + if (!dbConfig.isUseDocker()) { + return new Properties(); + } + + dbConfig.setDockerVersion("latest"); + return dbConfig.getDockerProperties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/CockroachSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/CockroachSetup.java new file mode 100644 index 000000000..879f895ee --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/CockroachSetup.java @@ -0,0 +1,41 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class CockroachSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + config.ddlMode("dropCreate"); + config.setDefaultPort(26257); + config.setUsername("root"); + config.setPassword(""); + config.setUrl("jdbc:postgresql://localhost:${port}/${databaseName}?sslmode=disable"); + config.setDriver("org.postgresql.Driver"); + config.datasourceDefaults(); + + return dockerProperties(config); + } + + private Properties dockerProperties(Config dbConfig) { + + if (!dbConfig.isUseDocker()) { + return new Properties(); + } + + dbConfig.setDockerVersion("v19.1.3"); + return dbConfig.getDockerProperties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/Config.java b/ebean-test/src/main/java/io/ebean/test/config/platform/Config.java new file mode 100644 index 000000000..391de1191 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/Config.java @@ -0,0 +1,454 @@ +package io.ebean.test.config.platform; + +import io.ebean.config.DatabaseConfig; +import io.ebean.datasource.DataSourceConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Properties; + +/** + * Config for a database / datasource with associated DDL mode and Docker configuration. + */ +class Config { + + private static final Logger log = LoggerFactory.getLogger(Config.class); + + /** + * Common optional docker parameters that we just transfer to docker properties. + */ + private static final String[] DOCKER_TEST_PARAMS = {"fastStartMode", "inMemory", "initSqlFile", "seedSqlFile", "adminUser", "adminPassword", "extraDb", "extraDb.dbName", "extraDb.username", "extraDb.password", "extraDb.initSqlFile", "extraDb.seedSqlFile"}; + private static final String[] DOCKER_PLATFORM_PARAMS = {"containerName", "image", "internalPort", "startMode", "stopMode", "shutdown", "maxReadyAttempts", "tmpfs", "collation", "characterSet"}; + + private static final String DDL_MODE_OPTIONS = "dropCreate, create, none, migration, createOnly or migrationDropCreate"; + + private final String db; + private final String platform; + private String dockerPlatform; + + private String databaseName; + + private final Properties properties; + + private int port; + + private String url; + private String driver; + private String schema; + private String username; + private String password; + + private final DatabaseConfig config; + + private boolean containerDropCreate; + + private final Properties dockerProperties = new Properties(); + + Config(String db, String platform, String databaseName, DatabaseConfig config) { + this.db = db; + this.platform = platform; + this.dockerPlatform = platform; + this.databaseName = databaseName; + this.config = config; + this.properties = config.getProperties(); + } + + void setSchemaFromDbName(String newDbName) { + this.schema = databaseName; + this.databaseName = newDbName; + } + + /** + * Set the docker platform name (when it is different from the test platform name). + * For example test platform name of "postgis" maps to "postgres" docker platform name. + */ + void setDockerPlatform(String dockerPlatform) { + this.dockerPlatform = dockerPlatform; + } + + void setDefaultPort(int defaultPort) { + String val = getPlatformKey("port", null); + if (val != null) { + port = Integer.parseInt(val); + } else { + port = defaultPort; + } + } + + void ddlMode(String defaultMode) { + String ddlMode = properties.getProperty("ebean.test.ddlMode", defaultMode); + if (ddlMode == null) { + throw new IllegalStateException("No ebean.test.ddlMode set? Expect one of " + DDL_MODE_OPTIONS); + } + switch (ddlMode.toLowerCase()) { + case "none": { + disableMigrationRun(); + break; + } + case "migrationonly": + case "migrationsonly": { + setMigrationRun(); + break; + } + case "migrationdropcreate": + case "migrationsdropcreate": + case "migration": + case "migrations": { + setMigrationRun(); + containerDropCreate = true; + break; + } + case "createonly": { + setCreate(); + break; + } + case "create": { + containerDropCreate = true; + setCreate(); + break; + } + case "dropcreate": { + setDropCreate(); + break; + } + case "runonly": { + setRunOnly(); + break; + } + default: + throw new IllegalStateException("Unknown ebean.test.ddlMode [" + ddlMode + "] expecting one of " + DDL_MODE_OPTIONS); + } + } + + private void setCreate() { + setDropCreate(); + config.setDdlCreateOnly(true); + setDdlProperty("createOnly"); + } + + private void setDropCreate() { + disableMigrationRun(); + config.setDdlGenerate(true); + config.setDdlRun(true); + setDdlProperty("generate"); + setDdlProperty("run"); + setDdlInitSeed(); + } + + private void setRunOnly() { + disableMigrationRun(); + config.setDdlGenerate(false); + config.setDdlRun(true); + setDdlProperty("run"); + setDdlInitSeed(); + } + + private void setDdlInitSeed() { + final String initSql = getKey("initSql", null); + if (initSql != null) { + setProperty("ebean." + db + ".ddl.initSql", initSql); + } + final String seedSql = getKey("seedSql", null); + if (seedSql != null) { + setProperty("ebean." + db + ".ddl.seedSql", seedSql); + } + } + + private void setMigrationRun() { + config.getMigrationConfig().setRunMigration(true); + setProperty("ebean." + db + ".migration.run", "true"); + } + + private void disableMigrationRun() { + System.setProperty("ddl.migration.run", "false"); + } + + /** + * Override the dataSource property. + */ + private void setDdlProperty(String key) { + setProperty("ebean." + db + ".ddl." + key, "true"); + } + + DataSourceConfig datasourceDefaults() { + return datasourceDefaults(platform); + } + + void extraDatasourceDefaults() { + datasourceDefaults("extraDb"); + } + + private DataSourceConfig datasourceDefaults(String platform) { + // default username to databaseName + if (username == null) { + throw new IllegalStateException("username not set?"); + } + if (password == null) { + throw new IllegalStateException("password not set?"); + } + + DataSourceConfig ds = new DataSourceConfig(); + ds.setUsername(datasourceProperty(platform, "username", username)); + ds.setPassword(datasourceProperty(platform, "password", password)); + ds.setOwnerUsername(datasourceProperty(platform, "ownerUsername", null)); + ds.setOwnerPassword(datasourceProperty(platform, "ownerPassword", null)); + ds.setUrl(datasourceProperty(platform, "url", url)); + String driverClass = datasourceProperty(platform, "driver", driver); + ds.setDriver(driverClass); + config.setDataSourceConfig(ds); + + log.info("Using jdbc settings - username:{} url:{} driver:{}", ds.getUsername(), ds.getUrl(), ds.getDriver()); + + if (driverClass != null) { + try { + Class.forName(driverClass); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("JDBC Driver " + driverClass + " does not appear to be in the classpath?"); + } + } + return ds; + } + + String datasourceProperty(String key, String defaultValue) { + return datasourceProperty(platform, key, defaultValue); + } + + /** + * Override the dataSource property. + */ + private String datasourceProperty(String platform, String key, String defaultValue) { + String val = getTestKey(platform, key, defaultValue); + if (val != null) { + setProperty("datasource." + db + "." + key, val); + } + return val; + } + + private void setProperty(String dsKey, String val) { + properties.setProperty(dsKey, val); + } + + void setUrl(String urlPattern) { + String val = getPlatformKey("url", urlPattern); + val = val.replace("${port}", String.valueOf(port)); + val = val.replace("${databaseName}", databaseName); + this.url = val; + } + + /** + * Append to the connection URL. + */ + void urlAppend(String dbSchemaSuffix) { + this.url += dbSchemaSuffix; + } + + void setDriver(String driver) { + this.driver = getPlatformKey("driver", driver); + } + + void setPasswordDefault() { + setPassword("test"); + } + + void setExtraDbPasswordDefault() { + setExtraDbPassword("test"); + } + + private String deriveDbSchema() { + String dbSchema = properties.getProperty("ebean.dbSchema", config.getDbSchema()); + dbSchema = properties.getProperty("ebean.test.dbSchema", dbSchema); + return getPlatformKey("schema", dbSchema); + } + + /** + * Set the username to default to database name. + */ + void setUsernameDefault() { + this.schema = first(deriveDbSchema()); + String defaultValue = schema != null ? schema : getPlatformKey("databaseName", this.databaseName); + this.username = getKey("username", defaultValue); + } + + void setUsernameDefaultSchema() { + this.username = getKey("username", schema); + } + + void setExtraUsernameDefault() { + this.username = getKey("extraDb.username", this.databaseName); + } + + private String first(String dbSchema) { + if (dbSchema == null) { + return null; + } + String[] schemas = dbSchema.split(","); + if (schemas.length > 1) { + // multiple schemas specified so just use the first one + return schemas[0]; + } + return dbSchema; + } + + String getUsername() { + return username; + } + + String getSchema() { + return schema; + } + + private void setExtraDbPassword(String password) { + this.password = getKey("extraDb.password", password); + } + + void setPassword(String password) { + this.password = getKey("password", password); + } + + void setUsername(String username) { + this.username = getPlatformKey("username", username); + } + + void setDatabaseName(String databaseName) { + this.databaseName = getPlatformKey("databaseName", databaseName); + } + + boolean isUseDocker() { + String val = getPlatformKey("useDocker", properties.getProperty("ebean.test.useDocker")); + return val == null || !val.equalsIgnoreCase("false"); + } + + void setDockerVersion(String version) { + String val = getPlatformKey("version", version); + dockerProperties.setProperty(dockerKey("version"), val); + + if (containerDropCreate) { + dockerProperties.setProperty(dockerKey("startMode"), "dropCreate"); + } + String mode = properties.getProperty("ebean.test.containerMode"); + if (mode != null) { + dockerProperties.setProperty(dockerKey("startMode"), mode); + } + initDockerProperties(); + } + + void setDockerContainerName(String containerName) { + dockerProperties.setProperty(dockerKey("containerName"), getPlatformKey("containerName", containerName)); + } + + void setDockerImage(String defaultImage) { + dockerProperties.setProperty(dockerKey("image"), getPlatformKey("image", defaultImage)); + } + + void setExtensions(String defaultValue) { + // ebean.test.postgres.extensions=hstore,pgcrypto + String val = getPlatformKey("extensions", defaultValue); + if (val != null) { + dockerProperties.setProperty(dockerKey("extensions"), trimExtensions(val)); + } + } + + String trimExtensions(String val) { + val = val.replaceAll(" ", ""); + val = val.replaceAll(",,", ","); + return val; + } + + private String getTestKey(String platform, String key, String defaultValue) { + return properties.getProperty("ebean.test." + platform + "." + key, defaultValue); + } + + String getPlatformKey(String key, String defaultValue) { + return properties.getProperty("ebean.test." + platform + "." + key, defaultValue); + } + + private String getKey(String key, String defaultValue) { + defaultValue = properties.getProperty("ebean.test." + key, defaultValue); + return properties.getProperty("ebean.test." + platform + "." + key, defaultValue); + } + + private void initDockerProperties() { + + dockerProperties.setProperty(dockerKey("port"), String.valueOf(port)); + dockerProperties.setProperty(dockerKey("dbName"), databaseName); + if (schema != null) { + dockerProperties.setProperty(dockerKey("schema"), schema); + } + dockerProperties.setProperty(dockerKey("username"), username); + dockerProperties.setProperty(dockerKey("password"), password); + dockerProperties.setProperty(dockerKey("url"), url); + if (driver != null) { + dockerProperties.setProperty(dockerKey("driver"), driver); + } + setDockerOptionalParameters(); + } + + private void setDockerOptionalParameters() { + + // check for shutdown mode on all containers + String mode = properties.getProperty("ebean.test.shutdown"); + if (mode != null && !ignoreDockerShutdown()) { + dockerProperties.setProperty(dockerKey("shutdown"), mode); + } + for (String key : DOCKER_TEST_PARAMS) { + String val = getKey(key, null); + val = properties.getProperty("docker." + platform + "." + key, val); + if (val != null) { + dockerProperties.setProperty(dockerKey(key), val); + } + } + for (String key : DOCKER_PLATFORM_PARAMS) { + String val = getPlatformKey(key, null); + val = properties.getProperty("docker." + platform + "." + key, val); + if (val != null) { + dockerProperties.setProperty(dockerKey(key), val); + } + } + } + + /** + * For local development we might want to ignore docker shutdown. + *

+ * So we just want the shutdown mode to be used on the CI server. + */ + boolean ignoreDockerShutdown() { + String localDev = properties.getProperty("ebean.test.localDevelopment", "~/.ebean/ignore-docker-shutdown"); + return ignoreDockerShutdown(localDev); + } + + boolean ignoreDockerShutdown(String localDev) { + + if (localDev.startsWith("~/")) { + File homeDir = new File(System.getProperty("user.home")); + return new File(homeDir, localDev.substring(2)).exists(); + } + + return new File(localDev).exists(); + } + + private String dockerKey(String key) { + return dockerPlatform + "." + key; + } + + Properties getDockerProperties() { + return dockerProperties; + } + + /** + * Pretty much only for SqlServer as we have the 2 platforms we need to choose from. + */ + void setDatabasePlatformName() { + String databasePlatformName = getPlatformKey("databasePlatformName", null); + if (databasePlatformName != null) { + setProperty("ebean." + db + ".databasePlatformName", databasePlatformName); + } + } + + /** + * Return the docker platform name. Should be a name that ebean-test-docker understands. + */ + String getDockerPlatform() { + return dockerPlatform; + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/ElasticSearchSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/ElasticSearchSetup.java new file mode 100644 index 000000000..011bfcbd2 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/ElasticSearchSetup.java @@ -0,0 +1,77 @@ +package io.ebean.test.config.platform; + +import io.ebean.docker.commands.ElasticConfig; +import io.ebean.docker.commands.ElasticContainer; + +import java.util.Properties; + +/** + * Setup and start a Docker container for ElasticSearch. + */ +class ElasticSearchSetup { + + private static final String[] DOCKER_PARAMS = {"containerName", "image", "internalPort", "startMode", "shutdown"}; + + private final Properties config; + + ElasticSearchSetup(Properties config) { + this.config = config; + } + + void run() { + ElasticConfig elasticConfig = readConfig(); + if (elasticConfig != null) { + new ElasticContainer(elasticConfig).start(); + } + } + + ElasticConfig readConfig() { + + String version = read("version", null); + if (version == null) { + // we need an explicit version to run + return null; + } + + return new ElasticConfig(version, populateDockerProperties(version)); + } + + private Properties populateDockerProperties(String version) { + + PropertiesBuilder properties = new PropertiesBuilder(); + + String mode = config.getProperty("ebean.test.shutdown"); + if (mode != null) { + properties.set("shutdown", mode); + } + + properties.set("version", version); + properties.set("port", read("port", "9201")); + for (String dockerParam : DOCKER_PARAMS) { + String val = read(dockerParam, null); + if (val != null) { + properties.set(dockerParam, val); + } + } + + return properties.build(); + } + + private String read(String key, String defaultValue) { + return config.getProperty("ebean.docstore.elastic." + key, defaultValue); + } + + private static class PropertiesBuilder { + + private Properties dockerProperties = new Properties(); + + private void set(String key, String val) { + dockerProperties.setProperty("elastic." + key, val); + } + + private Properties build() { + return dockerProperties; + } + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/H2Setup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/H2Setup.java new file mode 100644 index 000000000..5e94ae8d9 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/H2Setup.java @@ -0,0 +1,30 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class H2Setup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + config.ddlMode("create"); + config.setUsername("sa"); + config.setPassword(""); + config.setUrl("jdbc:h2:mem:${databaseName}"); + config.setDriver("org.h2.Driver"); + config.datasourceDefaults(); + + // return empty properties + return new Properties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + @Override + public boolean isLocal() { + return true; + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/HanaSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/HanaSetup.java new file mode 100644 index 000000000..8ad014b70 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/HanaSetup.java @@ -0,0 +1,67 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class HanaSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + config.setDatabasePlatformName(); + + config.ddlMode("dropCreate"); + int instanceNumber = Integer.parseInt(config.getPlatformKey("instanceNumber", "90")); + if (instanceNumber >= 0 && instanceNumber <= 99) { + config.setDefaultPort(30017 + (instanceNumber * 100)); + } else { + config.setDefaultPort(39017); + } + config.setUsernameDefault(); + config.setUsername("SYSTEM"); + config.setPassword("HXEHana1"); + config.setDatabaseName("HXE"); + config.setUrl("jdbc:sap://localhost:${port}/?databaseName=${databaseName}"); + String schema = config.getSchema(); + if (schema != null && !schema.equals(config.getUsername())) { + config.urlAppend("¤tSchema=" + schema); + } + config.setDriver("com.sap.db.jdbc.Driver"); + config.datasourceDefaults(); + + return dockerProperties(config); + } + + private Properties dockerProperties(Config dbConfig) { + + if (!dbConfig.isUseDocker()) { + return new Properties(); + } + + dbConfig.setDockerVersion("latest"); + + setDockerProperty("agreeToSapLicense", String.valueOf(false), dbConfig); + setDockerProperty("passwordsUrl", null, dbConfig); + setDockerProperty("mountsDirectory", null, dbConfig); + setDockerProperty("instanceNumber", null, dbConfig); + + return dbConfig.getDockerProperties(); + } + + @Override + public boolean isLocal() { + return false; + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + private void setDockerProperty(String key, String defaultValue, Config dbConfig) { + String value = dbConfig.getPlatformKey(key, defaultValue); + if (value != null) { + dbConfig.getDockerProperties().put("hana." + key, value); + } + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/MariaDBSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/MariaDBSetup.java new file mode 100644 index 000000000..4e242d4b0 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/MariaDBSetup.java @@ -0,0 +1,40 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class MariaDBSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + int defaultPort = config.isUseDocker() ? 4306 : 3306; + + config.ddlMode("dropCreate"); + config.setDefaultPort(defaultPort); + config.setUsernameDefault(); + config.setPasswordDefault(); + config.setUrl("jdbc:mariadb://localhost:${port}/${databaseName}?useLegacyDatetimeCode=false"); + config.datasourceDefaults(); + + return dockerProperties(config); + } + + private Properties dockerProperties(Config dbConfig) { + if (!dbConfig.isUseDocker()) { + return new Properties(); + } + dbConfig.setDockerVersion("10"); + return dbConfig.getDockerProperties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/MySqlSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/MySqlSetup.java new file mode 100644 index 000000000..3939ffbec --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/MySqlSetup.java @@ -0,0 +1,53 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class MySqlSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + int defaultPort = config.isUseDocker() ? 4306 : 3306; + + config.ddlMode("dropCreate"); + config.setDefaultPort(defaultPort); + config.setUsernameDefault(); + config.setPasswordDefault(); + config.setUrl("jdbc:mysql://localhost:${port}/${databaseName}"); + config.setDriver(defaultDriver()); + config.datasourceDefaults(); + + return dockerProperties(config); + } + + private String defaultDriver() { + try { + String newDriver = "com.mysql.cj.jdbc.Driver"; + Class.forName(newDriver); + return newDriver; + } catch (ClassNotFoundException e) { + return "com.mysql.jdbc.Driver"; + } + } + + private Properties dockerProperties(Config dbConfig) { + + if (!dbConfig.isUseDocker()) { + return new Properties(); + } + + dbConfig.setDockerVersion("8.0"); + return dbConfig.getDockerProperties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/NuoDBSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/NuoDBSetup.java new file mode 100644 index 000000000..b339c24a2 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/NuoDBSetup.java @@ -0,0 +1,45 @@ +package io.ebean.test.config.platform; + +import io.ebean.datasource.DataSourceConfig; + +import java.util.Properties; + +class NuoDBSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + // use testdb as our standard db name and instead use schema + config.setSchemaFromDbName("testdb"); + config.ddlMode("dropCreate"); + config.setDefaultPort(8888); + config.setUsernameDefaultSchema(); + config.setPasswordDefault(); + config.setUrl("jdbc:com.nuodb://localhost/testdb"); + config.setDriver("com.nuodb.jdbc.Driver"); + + final DataSourceConfig dsConfig = config.datasourceDefaults(); + dsConfig.setSchema(config.getSchema()); + return dockerProperties(config); + } + + private Properties dockerProperties(Config dbConfig) { + + if (!dbConfig.isUseDocker()) { + return new Properties(); + } + + dbConfig.setDockerVersion("4.0.0"); + return dbConfig.getDockerProperties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/OracleSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/OracleSetup.java new file mode 100644 index 000000000..b93660615 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/OracleSetup.java @@ -0,0 +1,41 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class OracleSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + config.ddlMode("dropCreate"); + config.setDefaultPort(1521); + config.setUsernameDefault(); + config.setPasswordDefault(); + config.setDatabaseName("XE"); + config.setUrl("jdbc:oracle:thin:@localhost:${port}:${databaseName}"); + config.setDriver("oracle.jdbc.driver.OracleDriver"); + config.datasourceDefaults(); + return dockerProperties(config); + } + + private Properties dockerProperties(Config dbConfig) { + + if (!dbConfig.isUseDocker()) { + return new Properties(); + } + + dbConfig.setDockerVersion("latest"); + return dbConfig.getDockerProperties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/PlatformAutoConfig.java b/ebean-test/src/main/java/io/ebean/test/config/platform/PlatformAutoConfig.java new file mode 100644 index 000000000..dc87516f2 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/PlatformAutoConfig.java @@ -0,0 +1,157 @@ +package io.ebean.test.config.platform; + +import io.ebean.config.DatabaseConfig; +import io.ebean.docker.container.ContainerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static java.util.concurrent.CompletableFuture.allOf; +import static java.util.concurrent.CompletableFuture.runAsync; + +public class PlatformAutoConfig { + + private static final Logger log = LoggerFactory.getLogger(PlatformAutoConfig.class); + + /** + * Known platforms we can setup locally or via docker container. + */ + private static final Map KNOWN_PLATFORMS = new HashMap<>(); + + static { + KNOWN_PLATFORMS.put("h2", new H2Setup()); + KNOWN_PLATFORMS.put("sqlite", new SqliteSetup()); + KNOWN_PLATFORMS.put("postgres", new PostgresSetup()); + KNOWN_PLATFORMS.put("postgis", new PostgisSetup()); + KNOWN_PLATFORMS.put("nuodb", new NuoDBSetup()); + KNOWN_PLATFORMS.put("mysql", new MySqlSetup()); + KNOWN_PLATFORMS.put("mariadb", new MariaDBSetup()); + KNOWN_PLATFORMS.put("sqlserver", new SqlServerSetup()); + KNOWN_PLATFORMS.put("oracle", new OracleSetup()); + KNOWN_PLATFORMS.put("clickhouse", new ClickHouseSetup()); + KNOWN_PLATFORMS.put("cockroach", new CockroachSetup()); + KNOWN_PLATFORMS.put("hana", new HanaSetup()); + } + + private final DatabaseConfig config; + + private final Properties properties; + + private String db; + + private String platform; + + private PlatformSetup platformSetup; + + private String databaseName; + + public PlatformAutoConfig(String db, DatabaseConfig config) { + this.db = db; + this.config = config; + this.properties = config.getProperties(); + } + + /** + * Configure the DataSource for the extra database. + */ + public void configExtraDataSource() { + determineTestPlatform(); + if (isKnownPlatform()) { + databaseName = config.getName(); + db = config.getName(); + + Config config = new Config(db, platform, databaseName, this.config); + platformSetup.setupExtraDbDataSource(config); + log.debug("configured dataSource for extraDb name:{} url:{}", db, this.config.getDataSourceConfig().getUrl()); + } + } + + /** + * Run setting up for testing. + */ + public void run() { + determineTestPlatform(); + if (isKnownPlatform()) { + readDbName(); + setupForTesting(); + } + } + + private void setupForTesting() { + // start containers in parallel + RedisSetup.run(properties); + allOf(runAsync(this::setupElasticSearch), runAsync(this::setupDatabase)).join(); + } + + private void setupElasticSearch() { + new ElasticSearchSetup(properties).run(); + } + + private void setupDatabase() { + Config config = new Config(db, platform, databaseName, this.config); + Properties dockerProperties = platformSetup.setup(config); + if (!dockerProperties.isEmpty()) { + if (isDebug()) { + log.info("Docker properties: {}", dockerProperties); + } else { + log.debug("Docker properties: {}", dockerProperties); + } + // start the docker container with appropriate configuration + new ContainerFactory(dockerProperties, config.getDockerPlatform()).startContainers(); + } + } + + private boolean isDebug() { + String val = properties.getProperty("ebean.test.debug"); + return (val != null && val.equalsIgnoreCase("true")); + } + + private void readDbName() { + databaseName = properties.getProperty("ebean.test.dbName"); + if (databaseName == null) { + if (inMemoryDb()) { + databaseName = "test_db"; + } else { + throw new IllegalStateException("ebean.test.dbName is not set but required for testing configuration with platform " + platform); + } + } + } + + private boolean inMemoryDb() { + return platformSetup.isLocal(); + } + + /** + * Return true if we match a known platform and know how to set it up for testing (via docker usually). + */ + private boolean isKnownPlatform() { + if (platform == null) { + return false; + } + this.platformSetup = KNOWN_PLATFORMS.get(platform); + if (platformSetup == null) { + log.warn("unknown platform {} - skipping platform setup", platform); + } + return platformSetup != null; + } + + /** + * Determine the platform we are going to use to run testing. + */ + private void determineTestPlatform() { + String testPlatform = properties.getProperty("ebean.test.platform"); + if (testPlatform != null && !testPlatform.isEmpty()) { + if (db == null) { + platform = testPlatform.trim(); + db = "db"; + } else { + // using command line system property to test alternate platform + // and we expect db to match a platform name + platform = db; + } + } + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/PlatformSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/PlatformSetup.java new file mode 100644 index 000000000..8530ce2d7 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/PlatformSetup.java @@ -0,0 +1,23 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +interface PlatformSetup { + + /** + * Return true if a local database (H2 and Sqlite - don't need a database name to be configured). + */ + boolean isLocal(); + + /** + * Run the setup for the given platform (set DataSource and DDL configuration). + * + * Return the properties used to configure the docker container. + */ + Properties setup(Config dbConfig); + + /** + * Set DataSource configuration for the extra database. + */ + void setupExtraDbDataSource(Config config); +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/PostgisSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/PostgisSetup.java new file mode 100644 index 000000000..8180dc425 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/PostgisSetup.java @@ -0,0 +1,57 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +/** + * A variation of Postgres that expected Postgis extension support. + * + * Uses mdillon/postgis image by default. + */ +class PostgisSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + int defaultPort = config.isUseDocker() ? 7432 : 5432; + + config.setDockerPlatform("postgres"); + config.ddlMode("dropCreate"); + config.setDefaultPort(defaultPort); + config.setUsernameDefault(); + config.setPasswordDefault(); + config.setDriver("org.postgis.DriverWrapperLW"); + config.setUrl("jdbc:postgresql_lwgis://localhost:${port}/${databaseName}"); + + String schema = config.getSchema(); + if (schema != null && !schema.equals(config.getUsername())) { + config.urlAppend("?currentSchema=" + schema); + } + config.datasourceDefaults(); + + return dockerProperties(config); + } + + private Properties dockerProperties(Config config) { + + if (!config.isUseDocker()) { + return new Properties(); + } + + config.setExtensions("hstore,pgcrypto,postgis"); + config.setDockerImage("mdillon/postgis"); + config.setDockerContainerName("postgis"); + config.setDockerVersion("10"); + return config.getDockerProperties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/PostgresSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/PostgresSetup.java new file mode 100644 index 000000000..8e9045408 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/PostgresSetup.java @@ -0,0 +1,57 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class PostgresSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + int defaultPort = config.isUseDocker() ? 6432 : 5432; + + config.ddlMode("dropCreate"); + config.setDefaultPort(defaultPort); + config.setUsernameDefault(); + config.setPasswordDefault(); + config.setUrl("jdbc:postgresql://localhost:${port}/${databaseName}"); + + String schema = config.getSchema(); + if (schema != null && !schema.equals(config.getUsername())) { + config.urlAppend("?currentSchema=" + schema); + } + config.setDriver("org.postgresql.Driver"); + config.datasourceDefaults(); + + return dockerProperties(config); + } + + private Properties dockerProperties(Config config) { + + if (!config.isUseDocker()) { + return new Properties(); + } + + config.setDockerVersion("12"); + config.setExtensions("hstore,pgcrypto"); + return config.getDockerProperties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + + int defaultPort = config.isUseDocker() ? 6432 : 5432; + + config.setDefaultPort(defaultPort); + config.setExtraUsernameDefault(); + config.setExtraDbPasswordDefault(); + config.setUrl("jdbc:postgresql://localhost:${port}/${databaseName}"); + config.setDriver("org.postgresql.Driver"); + config.extraDatasourceDefaults(); + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/RedisSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/RedisSetup.java new file mode 100644 index 000000000..70580bd81 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/RedisSetup.java @@ -0,0 +1,20 @@ +package io.ebean.test.config.platform; + +import io.ebean.docker.commands.RedisConfig; +import io.ebean.docker.commands.RedisContainer; + +import java.util.Properties; + +class RedisSetup { + + static void run(Properties properties) { + + String version = properties.getProperty("ebean.test.redis"); + version = properties.getProperty("ebean.test.redis.version", version); + if (version != null) { + RedisConfig redisConfig = new RedisConfig(version, properties); + RedisContainer container = new RedisContainer(redisConfig); + container.start(); + } + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/SqlServerSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/SqlServerSetup.java new file mode 100644 index 000000000..2dc68b5d7 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/SqlServerSetup.java @@ -0,0 +1,43 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class SqlServerSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + config.setDatabasePlatformName(); + + config.ddlMode("dropCreate"); + config.setDefaultPort(1433); + config.setUsernameDefault(); + config.setPassword("SqlS3rv#r"); + config.setUrl("jdbc:sqlserver://localhost:${port};databaseName=${databaseName}"); + config.setDriver("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + config.datasourceDefaults(); + + return dockerProperties(config); + } + + private Properties dockerProperties(Config dbConfig) { + + if (!dbConfig.isUseDocker()) { + return new Properties(); + } + + dbConfig.setDockerVersion("2019-GA-ubuntu-16.04"); + return dbConfig.getDockerProperties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported yet + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/SqliteSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/SqliteSetup.java new file mode 100644 index 000000000..a8a9a0de0 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/SqliteSetup.java @@ -0,0 +1,31 @@ +package io.ebean.test.config.platform; + +import java.util.Properties; + +class SqliteSetup implements PlatformSetup { + + @Override + public Properties setup(Config config) { + + config.ddlMode("dropCreate"); + config.setUsername(""); + config.setPassword(""); + config.setUrl("jdbc:sqlite:${databaseName}"); + config.setDriver("org.sqlite.JDBC"); + config.datasourceProperty("isolationlevel", "read_uncommitted"); + config.datasourceDefaults(); + + // return empty properties + return new Properties(); + } + + @Override + public void setupExtraDbDataSource(Config config) { + // not supported + } + + @Override + public boolean isLocal() { + return true; + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/provider/FixedEncryptKey.java b/ebean-test/src/main/java/io/ebean/test/config/provider/FixedEncryptKey.java new file mode 100644 index 000000000..04c8cafb3 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/provider/FixedEncryptKey.java @@ -0,0 +1,19 @@ +package io.ebean.test.config.provider; + +import io.ebean.config.EncryptKey; + +class FixedEncryptKey implements EncryptKey { + + private final String key; + + FixedEncryptKey(String key) { + this.key = key; + } + + @Override + public String getStringValue() { + return key; + } + + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/provider/FixedEncryptKeyManager.java b/ebean-test/src/main/java/io/ebean/test/config/provider/FixedEncryptKeyManager.java new file mode 100644 index 000000000..b386daeae --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/provider/FixedEncryptKeyManager.java @@ -0,0 +1,27 @@ +package io.ebean.test.config.provider; + +import io.ebean.config.EncryptKey; +import io.ebean.config.EncryptKeyManager; + +class FixedEncryptKeyManager implements EncryptKeyManager { + + private final FixedEncryptKey key; + + FixedEncryptKeyManager(String fixedKey) { + this.key = new FixedEncryptKey(fixedKey); + } + + /** + * Initialise the key manager. + */ + @Override + public void initialise() { + + } + + @Override + public EncryptKey getEncryptKey(String tableName, String columnName) { + return key; + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/provider/ProviderAutoConfig.java b/ebean-test/src/main/java/io/ebean/test/config/provider/ProviderAutoConfig.java new file mode 100644 index 000000000..c08d6466c --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/provider/ProviderAutoConfig.java @@ -0,0 +1,79 @@ +package io.ebean.test.config.provider; + +import io.ebean.config.CurrentTenantProvider; +import io.ebean.config.CurrentUserProvider; +import io.ebean.config.DatabaseConfig; +import io.ebean.config.EncryptKeyManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Properties; + +/** + * Auto configuration of User and Tenant providers and Encrypt key manager for testing purposes. + */ +public class ProviderAutoConfig { + + private static final Logger log = LoggerFactory.getLogger(ProviderAutoConfig.class); + + private final DatabaseConfig config; + private final Properties properties; + + public ProviderAutoConfig(DatabaseConfig config) { + this.config = config; + this.properties = config.getProperties(); + } + + public void run() { + + int providerSetFlag = 0; + + CurrentUserProvider provider = config.getCurrentUserProvider(); + if (provider == null) { + providerSetFlag = 1; + config.setCurrentUserProvider(new WhoUserProvider()); + } + + CurrentTenantProvider tenantProvider = config.getCurrentTenantProvider(); + if (tenantProvider == null) { + providerSetFlag += 2; + config.setCurrentTenantProvider(new WhoTenantProvider()); + } + + EncryptKeyManager keyManager = config.getEncryptKeyManager(); + if (keyManager == null) { + // Must be 16 Chars for Oracle function + String keyVal = properties.getProperty("ebean.test.encryptKey", "simple0123456789"); + log.debug("for testing - using FixedEncryptKeyManager() keyVal:{}", keyVal); + config.setEncryptKeyManager(new FixedEncryptKeyManager(keyVal)); + } + + if (providerSetFlag > 0) { + log.info(msg(providerSetFlag)); + } + } + + String msg(int providerSetFlag) { + String msg = msgProvider(providerSetFlag); + String usage = msgUsage(providerSetFlag); + return "for testing purposes "+msg+" has been configured. Use io.ebean.test.UserContext to "+usage+" in tests."; + } + + private String msgProvider(int providerSetFlag) { + switch (providerSetFlag) { + case 1: return "a current user provider"; + case 2: return "a current tenant provider"; + case 3: return "a current user and tenant provider"; + } + return "[unexpected??]"; + } + + private String msgUsage(int providerSetFlag) { + switch (providerSetFlag) { + case 1: return "set current user"; + case 2: return "set current tenant"; + case 3: return "set current user and tenant"; + } + return "[unexpected??]"; + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/provider/WhoTenantProvider.java b/ebean-test/src/main/java/io/ebean/test/config/provider/WhoTenantProvider.java new file mode 100644 index 000000000..80946618d --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/provider/WhoTenantProvider.java @@ -0,0 +1,12 @@ +package io.ebean.test.config.provider; + +import io.ebean.config.CurrentTenantProvider; +import io.ebean.test.UserContext; + +class WhoTenantProvider implements CurrentTenantProvider{ + + @Override + public Object currentId() { + return UserContext.currentTenantId(); + } +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/provider/WhoUserProvider.java b/ebean-test/src/main/java/io/ebean/test/config/provider/WhoUserProvider.java new file mode 100644 index 000000000..def0ccd9b --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/provider/WhoUserProvider.java @@ -0,0 +1,12 @@ +package io.ebean.test.config.provider; + +import io.ebean.config.CurrentUserProvider; +import io.ebean.test.UserContext; + +class WhoUserProvider implements CurrentUserProvider { + + @Override + public Object currentUser() { + return UserContext.currentUserId(); + } +} diff --git a/ebean-test/src/main/resources/META-INF/services/io.ebean.config.AutoConfigure b/ebean-test/src/main/resources/META-INF/services/io.ebean.config.AutoConfigure new file mode 100644 index 000000000..e6d71769f --- /dev/null +++ b/ebean-test/src/main/resources/META-INF/services/io.ebean.config.AutoConfigure @@ -0,0 +1 @@ +io.ebean.test.config.AutoConfigureForTesting diff --git a/ebean-test/src/main/resources/META-INF/services/io.ebeaninternal.api.SpiLoggerFactory b/ebean-test/src/main/resources/META-INF/services/io.ebeaninternal.api.SpiLoggerFactory new file mode 100644 index 000000000..452778928 --- /dev/null +++ b/ebean-test/src/main/resources/META-INF/services/io.ebeaninternal.api.SpiLoggerFactory @@ -0,0 +1 @@ +io.ebean.test.CapturingLoggerFactory diff --git a/ebean-test/src/test/java/io/ebean/test/DbJsonTest.java b/ebean-test/src/test/java/io/ebean/test/DbJsonTest.java new file mode 100644 index 000000000..dc841c88f --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/test/DbJsonTest.java @@ -0,0 +1,41 @@ +package io.ebean.test; + +import io.ebean.DB; +import org.junit.Test; +import org.test.BSimpleWithGen; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class DbJsonTest { + + @Test + public void of() { + + DB.find(BSimpleWithGen.class).delete(); + + BSimpleWithGen bean = new BSimpleWithGen("something"); + DB.save(bean); + + assertThat(bean.getVersion()).isEqualTo(1); + + DbJson.of(bean) + .withPlaceholder("_") + .replace("id", "whenModified") + .assertContentMatches("/bean/example-bean.json"); + + + BSimpleWithGen bean2 = new BSimpleWithGen("other"); + DB.save(bean2); + + final List beans = DB.find(BSimpleWithGen.class).findList(); + + DbJson.of(beans) + //.withPlaceholder("_") + .replace("id", "whenModified") + .assertContentMatches("/bean/example-list.json"); + + } +} diff --git a/ebean-test/src/test/java/io/ebean/test/UserContextTest.java b/ebean-test/src/test/java/io/ebean/test/UserContextTest.java new file mode 100644 index 000000000..750a256e0 --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/test/UserContextTest.java @@ -0,0 +1,54 @@ +package io.ebean.test; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class UserContextTest { + + @Test + public void set() { + + UserContext.set("u1", "t1"); + + assertEquals("u1", UserContext.currentUserId()); + assertEquals("t1", UserContext.currentTenantId()); + + UserContext.set("u2", "t1"); + + assertEquals("u2", UserContext.currentUserId()); + assertEquals("t1", UserContext.currentTenantId()); + + UserContext.set("u3", "t3"); + + assertEquals("u3", UserContext.currentUserId()); + assertEquals("t3", UserContext.currentTenantId()); + } + + @Test + public void setUserId() { + + UserContext.reset(); + UserContext.setUserId("u1"); + assertEquals("u1", UserContext.currentUserId()); + assertNull(UserContext.currentTenantId()); + + UserContext.setUserId("u2"); + assertEquals("u2", UserContext.currentUserId()); + assertNull(UserContext.currentTenantId()); + } + + + @Test + public void setTenantId() { + + UserContext.reset(); + UserContext.setTenantId("t1"); + assertEquals("t1", UserContext.currentTenantId()); + assertNull(UserContext.currentUserId()); + + UserContext.setTenantId("t2"); + assertEquals("t2", UserContext.currentTenantId()); + assertNull(UserContext.currentUserId()); + } +} diff --git a/ebean-test/src/test/java/io/ebean/test/config/platform/ConfigTest.java b/ebean-test/src/test/java/io/ebean/test/config/platform/ConfigTest.java new file mode 100644 index 000000000..93f0504a2 --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/test/config/platform/ConfigTest.java @@ -0,0 +1,163 @@ +package io.ebean.test.config.platform; + +import io.ebean.config.ServerConfig; +import io.ebean.datasource.DataSourceConfig; +import org.junit.Test; + +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class ConfigTest { + + @Test + public void trimExtensions() { + + Config config = new Config("db", "db", "db", new ServerConfig()); + + assertThat(config.trimExtensions("a,b")).isEqualTo("a,b"); + assertThat(config.trimExtensions(" a , b ")).isEqualTo("a,b"); + assertThat(config.trimExtensions(" a , , b ")).isEqualTo("a,b"); + } + + @Test + public void extraDbProperties_basic() { + + Properties p = new Properties(); + p.setProperty("ebean.test.extraDb", "other"); + + ServerConfig serverConfig = new ServerConfig(); + serverConfig.loadFromProperties(p); + + Config config = new Config("other", "postgres", "other", serverConfig); + + PostgresSetup postgresSetup = new PostgresSetup(); + postgresSetup.setupExtraDbDataSource(config); + + DataSourceConfig ds = serverConfig.getDataSourceConfig(); + assertThat(ds.getUsername()).isEqualTo("other"); + + p = serverConfig.getProperties(); + assertThat(p.getProperty("datasource.other.username")).isEqualTo("other"); + assertThat(p.getProperty("datasource.other.password")).isEqualTo("test"); + assertThat(p.getProperty("datasource.other.url")).isEqualTo("jdbc:postgresql://localhost:6432/other"); + } + + @Test + public void extraDbProperties_basic_extraDb_dbName() { + + Properties p = new Properties(); + p.setProperty("ebean.test.extraDb.dbName", "other"); + + ServerConfig serverConfig = new ServerConfig(); + serverConfig.loadFromProperties(p); + + Config config = new Config("other", "postgres", "other", serverConfig); + + PostgresSetup postgresSetup = new PostgresSetup(); + postgresSetup.setupExtraDbDataSource(config); + + DataSourceConfig ds = serverConfig.getDataSourceConfig(); + assertThat(ds.getUsername()).isEqualTo("other"); + + p = serverConfig.getProperties(); + assertThat(p.getProperty("datasource.other.username")).isEqualTo("other"); + assertThat(p.getProperty("datasource.other.password")).isEqualTo("test"); + assertThat(p.getProperty("datasource.other.url")).isEqualTo("jdbc:postgresql://localhost:6432/other"); + } + + @Test + public void extraDbProperties_withOptions() { + + Properties p = new Properties(); + p.setProperty("ebean.test.extraDb", "other1"); + p.setProperty("ebean.test.extraDb.dbName", "other_db_name"); + p.setProperty("ebean.test.extraDb.username", "other_user"); + p.setProperty("ebean.test.extraDb.password", "other_pwd"); + p.setProperty("ebean.test.extraDb.url", "other_url"); + + ServerConfig serverConfig = new ServerConfig(); + serverConfig.setName("scOther"); + serverConfig.loadFromProperties(p); + + Config config = new Config("other_db_name", "postgres", "other_db_name", serverConfig); + + PostgresSetup postgresSetup = new PostgresSetup(); + postgresSetup.setupExtraDbDataSource(config); + + + DataSourceConfig ds = serverConfig.getDataSourceConfig(); + assertThat(ds.getUsername()).isEqualTo("other_user"); + + p = serverConfig.getProperties(); + assertThat(p.getProperty("datasource.other_db_name.username")).isEqualTo("other_user"); + assertThat(p.getProperty("datasource.other_db_name.password")).isEqualTo("other_pwd"); + assertThat(p.getProperty("datasource.other_db_name.url")).isEqualTo("other_url"); + } + + + @Test + public void extraDbProperties_withExtraDbOptions() { + + Properties sourceProperties = new Properties(); + sourceProperties.setProperty("ebean.test.dbName", "main"); + sourceProperties.setProperty("ebean.test.extraDb.dbName", "central"); + + ServerConfig serverConfig = new ServerConfig(); + serverConfig.setName("main"); + serverConfig.loadFromProperties(sourceProperties); + + Config config = new Config("main", "postgres", "main", serverConfig); + + PostgresSetup postgresSetup = new PostgresSetup(); + postgresSetup.setup(config); + + Properties mainProps = serverConfig.getProperties(); + assertThat(mainProps.getProperty("datasource.main.username")).isEqualTo("main"); + + + ServerConfig centralConfig = new ServerConfig(); + centralConfig.setName("central"); + centralConfig.loadFromProperties(sourceProperties); + + Config extraConfig = new Config("central", "postgres", "central", serverConfig); + + postgresSetup = new PostgresSetup(); + postgresSetup.setupExtraDbDataSource(extraConfig); + + Properties centralProps = serverConfig.getProperties(); + assertThat(centralProps.getProperty("datasource.central.username")).isEqualTo("central"); + + } + + @Test + public void ignoreDockerShutdown() { + + Properties sourceProperties = new Properties(); + ServerConfig serverConfig = new ServerConfig(); + serverConfig.loadFromProperties(sourceProperties); + + Config config = new Config("main", "postgres", "main", serverConfig); + + assertThat(config.ignoreDockerShutdown("./src/test/resources/logback-test.xml")).isTrue(); + assertThat(config.ignoreDockerShutdown("./src/test/resources/file-does-not-exist")).isFalse(); + + assertThat(config.ignoreDockerShutdown("~/.ebean/ignore-docker-shutdown")).isTrue(); + assertThat(config.ignoreDockerShutdown()).isTrue(); + } + + @Test + public void ignoreDockerShutdown_viaProperties() { + + Properties sourceProperties = new Properties(); + sourceProperties.setProperty("ebean.test.localDevelopment", "./src/test/resources/logback-test.xml"); + + ServerConfig serverConfig = new ServerConfig(); + serverConfig.loadFromProperties(sourceProperties); + + Config config = new Config("main", "postgres", "main", serverConfig); + assertThat(config.ignoreDockerShutdown()).isTrue(); + } + +} diff --git a/ebean-test/src/test/java/io/ebean/test/config/provider/ProviderAutoConfigTest.java b/ebean-test/src/test/java/io/ebean/test/config/provider/ProviderAutoConfigTest.java new file mode 100644 index 000000000..3468e9698 --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/test/config/provider/ProviderAutoConfigTest.java @@ -0,0 +1,25 @@ +package io.ebean.test.config.provider; + +import io.ebean.config.ServerConfig; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ProviderAutoConfigTest { + + private ProviderAutoConfig config = new ProviderAutoConfig(new ServerConfig()); + + @Test + public void msg() { + + assertEquals(config.msg(1), "for testing purposes a current user provider has been configured. Use io.ebean.test.UserContext to set current user in tests."); + assertEquals(config.msg(2), "for testing purposes a current tenant provider has been configured. Use io.ebean.test.UserContext to set current tenant in tests."); + assertEquals(config.msg(3), "for testing purposes a current user and tenant provider has been configured. Use io.ebean.test.UserContext to set current user and tenant in tests."); + } + + @Test + public void msgUnexpected() { + // rather than fail ... + assertEquals(config.msg(4), "for testing purposes [unexpected??] has been configured. Use io.ebean.test.UserContext to [unexpected??] in tests."); + } +} diff --git a/ebean-test/src/test/java/org/test/BSimpleWithGen.java b/ebean-test/src/test/java/org/test/BSimpleWithGen.java new file mode 100644 index 000000000..cf3c7f2f6 --- /dev/null +++ b/ebean-test/src/test/java/org/test/BSimpleWithGen.java @@ -0,0 +1,65 @@ +package org.test; + +import io.ebean.annotation.WhenModified; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Transient; +import javax.persistence.Version; +import java.time.Instant; +import java.util.List; +import java.util.Map; + +@Entity +public class BSimpleWithGen { + + @Id + private Integer id; + + private String name; + + @Transient + private Map> someMap; + + @WhenModified + private Instant whenModified; + + @Version + private long version; + + public BSimpleWithGen(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map> getSomeMap() { + return someMap; + } + + public void setSomeMap(Map> someMap) { + this.someMap = someMap; + } + + public Instant getWhenModified() { + return whenModified; + } + + public long getVersion() { + return version; + } +} diff --git a/ebean-test/src/test/java/org/test/ForTestsBeforeAfterTest.java b/ebean-test/src/test/java/org/test/ForTestsBeforeAfterTest.java new file mode 100644 index 000000000..7392b117d --- /dev/null +++ b/ebean-test/src/test/java/org/test/ForTestsBeforeAfterTest.java @@ -0,0 +1,49 @@ +package org.test; + +import io.ebean.DB; +import io.ebean.annotation.Transactional; +import io.ebean.test.ForTests; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class ForTestsBeforeAfterTest { + + private ForTests.RollbackAll rollbackAll; + + @Before + public void before() { + rollbackAll = ForTests.createRollbackAll(); + } + + @After + public void after() { + rollbackAll.close(); + assertThat(getCount()).isEqualTo(0); + } + + + @Test + public void createRollbackAll() { + + doInsert(); + assertThat(getCount()).isEqualTo(1); + } + + private int getCount() { + return DB.find(BSimpleWithGen.class) + .where().eq("name", "ForTestsBeforeAfterTest") + .findCount(); + } + + @Transactional + private void doInsert() { + + BSimpleWithGen bean = new BSimpleWithGen("ForTestsBeforeAfterTest"); + DB.save(bean); + } + +} diff --git a/ebean-test/src/test/java/org/test/ForTestsTest.java b/ebean-test/src/test/java/org/test/ForTestsTest.java new file mode 100644 index 000000000..bdf766ce9 --- /dev/null +++ b/ebean-test/src/test/java/org/test/ForTestsTest.java @@ -0,0 +1,54 @@ +package org.test; + +import io.ebean.DB; +import io.ebean.Transaction; +import io.ebean.annotation.Transactional; +import io.ebean.test.ForTests; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ForTestsTest { + + @Test + public void noTransactional_expect_noTransactionEnterExitCalled() { + + ForTests.noTransactional(this::checkForTransactional); + } + + @Test + public void enableTransactional() { + + ForTests.enableTransactional(false); + + checkForTransactional(); + + ForTests.enableTransactional(true); + } + + @Transactional + private void checkForTransactional() { + + final Transaction transaction = DB.currentTransaction(); + assertThat(transaction).isNull(); + } + + @Test + public void rollbackTransactions() { + + DB.find(BSimpleWithGen.class).delete(); + + ForTests.rollbackAll(this::doInsert); + + final int count = DB.find(BSimpleWithGen.class).findCount(); + assertThat(count).isEqualTo(0); + } + + @Transactional + private void doInsert() { + + BSimpleWithGen bean = new BSimpleWithGen("doInsert"); + DB.save(bean); + } + +} diff --git a/ebean-test/src/test/resources/application-test.yml b/ebean-test/src/test/resources/application-test.yml new file mode 100644 index 000000000..c5f3914d2 --- /dev/null +++ b/ebean-test/src/test/resources/application-test.yml @@ -0,0 +1,78 @@ +datasource: + db: + username: sa + password: + databaseUrl: jdbc:h2:mem:tests + databaseDriver: org.h2.Driver + +ebean: +# migration: +# run: false +# +# ddl: +# generate: true +# run: true +## createOnly: true + + docstore: + url: http://127.0.0.1:9200 + active: true + generateMapping: true + dropCreate: true +# useDocker: true + elasticVersion: 5.6 + +# create: true + + test: + redis: latest + platform: mariadb #, postgres, mysql, mariadb, oracle, sqlserver, hana +# useDocker: false +# dockerMode: dropCreate + ddlMode: dropCreate # none | dropCreate | create | migrations + dbName: junk + postgres: + version: 9.6 + extensions: pgcryto, hstore + username: asd + password: test + url: asd + driver: asd + + + +docker: + postgres: +# port: 6432 + version: 9.6 + extensions: pgcryto, hstore + username: ${test_db} + password: test + databaseUrl: jdbc:postgresql://localhost:6432/${test_db} + databaseDriver: org.postgresql.Driver + + mysql: + version: 5.6 + username: ${test_db} + password: test + databaseUrl: jdbc:mysql://localhost:4306/${test_db} + databaseDriver: com.mysql.jdbc.Driver + + sqlserver: + version: 2017-CE + username: ${test_db} + password: SqlS3rv#r + databaseUrl: jdbc:sqlserver://localhost:1433;databaseName=${test_db} + databaseDriver: com.microsoft.sqlserver.jdbc.SQLServerDriver + + oracle: + username: ${test_db} + password: test + databaseUrl: jdbc:oracle:thin:@127.0.0.1:1521:XE + databaseDriver: oracle.jdbc.driver.OracleDriver + + hana: + username: ${test_db} + password: HXEHana1 + databaseUrl: jdbc:sap://localhost:39017/?databaseName=HXE + databaseDriver: com.sap.db.jdbc.Driver diff --git a/ebean-test/src/test/resources/bean/example-bean.json b/ebean-test/src/test/resources/bean/example-bean.json new file mode 100644 index 000000000..593babff3 --- /dev/null +++ b/ebean-test/src/test/resources/bean/example-bean.json @@ -0,0 +1,6 @@ +{ + "id": _, + "name": "something", + "whenModified": _, + "version": 1 +} diff --git a/ebean-test/src/test/resources/bean/example-list.json b/ebean-test/src/test/resources/bean/example-list.json new file mode 100644 index 000000000..21264b0a7 --- /dev/null +++ b/ebean-test/src/test/resources/bean/example-list.json @@ -0,0 +1,11 @@ +[ { + "id": "*", + "name": "something", + "whenModified": "*", + "version": 1 +}, { + "id": "*", + "name": "other", + "whenModified": "*", + "version": 1 +} ] diff --git a/ebean-test/src/test/resources/docker-run.properties b/ebean-test/src/test/resources/docker-run.properties new file mode 100644 index 000000000..275a4c76e --- /dev/null +++ b/ebean-test/src/test/resources/docker-run.properties @@ -0,0 +1,21 @@ +postgres.version=9.6 +postgres.dbName=test_db +postgres.dbUser=test_user +postgres.dbPassword=test +postgres.dbExtensions=hstore,pgcrypto + + +sqlserver.version=2017-CU2 +#sqlserver.port=1433 +#sqlserver.dbName=test_db +#sqlserver.dbUser=test_user +#sqlserver.dbPassword=SqlS3rv#r + + +hana.version=2.00.033.00.20180925.2 +hana.port=39117 +hana.instanceNumber=91 +hana.passwordsUrl=file:///hana/mounts/passwords.json +hana.mountsDirectory=/data/dockermounts +# agree to the SAP license (https://www.sap.com/docs/download/cmp/2016/06/sap-hana-express-dev-agmt-and-exhibit.pdf) +hana.agreeToSapLicense=false \ No newline at end of file diff --git a/ebean-test/src/test/resources/ebean.mf b/ebean-test/src/test/resources/ebean.mf new file mode 100644 index 000000000..86b6aa821 --- /dev/null +++ b/ebean-test/src/test/resources/ebean.mf @@ -0,0 +1,2 @@ +entity-packages: org.test +transactional-packages: org.test diff --git a/ebean-test/src/test/resources/logback-test.xml b/ebean-test/src/test/resources/logback-test.xml new file mode 100644 index 000000000..ce7ad5968 --- /dev/null +++ b/ebean-test/src/test/resources/logback-test.xml @@ -0,0 +1,22 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 27b584cb9..9450b86c2 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,7 @@ ebean-core ebean-autotune + ebean-test