#2068 - Add ebean-test as module

This commit is contained in:
rob bygrave
2020-10-08 23:48:19 +13:00
parent ab283efed7
commit bc055fea25
47 changed files with 2730 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.4.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ebean-test</artifactId>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean.test</groupId>
<artifactId>ebean-test-docker</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.14.0</version>
</dependency>
<!-- Including JAXB for DB Migration generation with Java 11+ -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.2</version>
</dependency>
<!-- Not strictly required but bring in H2 Driver because we use it so much -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.12</version>
<scope>test</scope>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>6.2.2.jre8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sap.cloud.db.jdbc</groupId>
<artifactId>ngdbc</artifactId>
<version>2.3.48</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.repaint.maven</groupId>
<artifactId>tiles-maven-plugin</artifactId>
<version>2.17</version>
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:12.4.2</tile>
</tiles>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -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<String> 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<String> start() {
this.active = true;
return collect();
}
List<String> stop() {
this.active = false;
return collect();
}
List<String> collect() {
List<String> response = messages;
messages = new ArrayList<>();
return response;
}
}
@@ -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.
* <p>
* The logged SQL is accessed by LoggedSql.
* </p>
*/
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;
}
}
@@ -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.
*
* <pre>{@code
*
* DbJson.of(timedEntries)
* .replace("id", "eventTime")
* .assertContentMatches("/assertJson/full-1-timed.json");
*
* }</pre>
*/
public class DbJson {
/**
* Create a PrettyJson object that has the JSON form of the
* entity bean or beans.
*
* <pre>{@code
*
* DbJson.of(timedEntries)
* .replace("id", "eventTime")
* .assertContentMatches("/assertJson/full-1-timed.json");
*
* }</pre>
*/
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.
* <p>
* Typically we do this on generated properties such as id and timestamp properties.
* </p>
*/
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.
*
* <pre>{@code
*
* DbJson.of(timedEntries)
* .replace("id", "eventTime")
* .assertContentMatches("/assertJson/full-1-timed.json");
*
* }</pre>
*/
public void assertContentMatches(String resourcePath) {
assertThat(rawJson).isEqualTo(readResource(resourcePath));
}
}
}
@@ -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 <code>@Transactional</code> methods.
* <p>
* This is intended for testing purposes such that tests
* on code with {@code @Transactional} methods don't actually
* start or complete transactions.
* </p>
*
* @param enable Set false to disable {@code @Transactional} methods
*/
public static void enableTransactional(boolean enable) {
HelpScopeTrans.setEnabled(enable);
}
/**
* Run the closure with <code>@Transactional</code> 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.
* <p>
* 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.
* </p>
*
* @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).
* <p>
* In tests for <code>@Before</code> we create the rollbackAll and on
* <code>@After</code> we <code>close()</code> it effectively rolling
* back all changes made during test execution.
* </p>
*
* <pre>{@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
*
*
* }</pre>
*/
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.
* <p>
* We must ensure that <code>close()</code> is called.
* </p>
*/
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();
}
}
}
@@ -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 <code>io.ebean.SQL</code>.
* <p>
* 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.
* </p>
* <pre>{@code
*
* // start capturing SQL log messages
* LoggedSql.start();
*
* List<Customer> 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<String> sql = LoggedSql.stop();
*
* assertThat(sql).hasSize(2);
* assertThat(sql.get(0)).contains("from customer");
* assertThat(sql.get(1)).contains("into product");
*
* }</pre>
*/
public class LoggedSql {
private static CaptureLogger sqlLogger;
/**
* Internal use - register the logger for <code>io.ebean.SQL</code>.
*/
static SpiLogger register(DSpiLogger logger) {
if (sqlLogger == null) {
sqlLogger = new CaptureLogger(logger);
}
return sqlLogger;
}
/**
* Start the capture of the <code>io.ebean.SQL</code> messages.
*/
public static List<String> start() {
return sqlLogger.start();
}
/**
* Stop the capture of the <code>io.ebean.SQL</code> messages and return the messages/sql
* that was captured since the call to start().
*/
public static List<String> stop() {
return sqlLogger.stop();
}
/**
* Collect and return the messages/sql that was captured since the call to start() or collect().
* <p>
* Unlike stop() collection of messages will continue.
* </p>
*/
public static List<String> collect() {
return sqlLogger.collect();
}
}
@@ -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.
* <p>
* 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.
* <pre>{@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();
*
* }</pre>
*/
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<UserContext> {
@Override
protected UserContext initialValue() {
return new UserContext();
}
}
}
@@ -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.
* <p>
* 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();
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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.
* <p>
* 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;
}
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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("&currentSchema=" + 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);
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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<String, PlatformSetup> 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;
}
}
}
}
@@ -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);
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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??]";
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -0,0 +1 @@
io.ebean.test.config.AutoConfigureForTesting
@@ -0,0 +1 @@
io.ebean.test.CapturingLoggerFactory
@@ -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<BSimpleWithGen> beans = DB.find(BSimpleWithGen.class).findList();
DbJson.of(beans)
//.withPlaceholder("_")
.replace("id", "whenModified")
.assertContentMatches("/bean/example-list.json");
}
}
@@ -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());
}
}
@@ -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();
}
}
@@ -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.");
}
}
@@ -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<String, List<String>> 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<String, List<String>> getSomeMap() {
return someMap;
}
public void setSomeMap(Map<String, List<String>> someMap) {
this.someMap = someMap;
}
public Instant getWhenModified() {
return whenModified;
}
public long getVersion() {
return version;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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
@@ -0,0 +1,6 @@
{
"id": _,
"name": "something",
"whenModified": _,
"version": 1
}
@@ -0,0 +1,11 @@
[ {
"id": "*",
"name": "something",
"whenModified": "*",
"version": 1
}, {
"id": "*",
"name": "other",
"whenModified": "*",
"version": 1
} ]
@@ -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
+2
View File
@@ -0,0 +1,2 @@
entity-packages: org.test
transactional-packages: org.test
@@ -0,0 +1,22 @@
<configuration scan="true" scanPeriod="10 seconds">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.avaje" level="TRACE"/>
<logger name="io.ebean" level="INFO"/>
<logger name="io.ebean.docker" level="TRACE"/>
<logger name="io.ebean.DDL" level="DEBUG"/>
<logger name="io.ebean.SQL" level="TRACE"/>
<logger name="io.ebean.TXN" level="TRACE"/>
<logger name="io.ebean.SUM" level="TRACE"/>
</configuration>
+1
View File
@@ -39,6 +39,7 @@
<modules>
<module>ebean-core</module>
<module>ebean-autotune</module>
<module>ebean-test</module>
</modules>
</project>