diff --git a/modules/cratedb/build.gradle b/modules/cratedb/build.gradle new file mode 100644 index 00000000000..07f61137ea0 --- /dev/null +++ b/modules/cratedb/build.gradle @@ -0,0 +1,13 @@ +description = "Testcontainers :: JDBC :: PostgreSQL" + +dependencies { + annotationProcessor 'com.google.auto.service:auto-service:1.0.1' + compileOnly 'com.google.auto.service:auto-service:1.0.1' + + api project(':jdbc') + + testImplementation project(':jdbc-test') + testImplementation 'io.crate:crate-jdbc:2.6.0' + + compileOnly 'org.jetbrains:annotations:24.0.0' +} diff --git a/modules/cratedb/src/main/java/org/testcontainers/containers/CrateDBContainer.java b/modules/cratedb/src/main/java/org/testcontainers/containers/CrateDBContainer.java new file mode 100644 index 00000000000..b3be4486503 --- /dev/null +++ b/modules/cratedb/src/main/java/org/testcontainers/containers/CrateDBContainer.java @@ -0,0 +1,130 @@ +package org.testcontainers.containers; + +import org.jetbrains.annotations.NotNull; +import org.testcontainers.containers.wait.strategy.DockerHealthcheckWaitStrategy;import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.containers.wait.strategy.Wait;import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.List;import java.util.Set; + +public class CrateDBContainer> extends JdbcDatabaseContainer { + + public static final String NAME = "crate"; + + public static final String IMAGE = "crate"; + + public static final String DEFAULT_TAG = "latest"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("crate"); + + public static final Integer CRATEDB_PG_PORT = 5432; + public static final Integer CRATEDB_HTTP_PORT = 4200; + + private String databaseName = "crate"; + + private String username = "crate"; + + private String password = "crate"; + + + /** + * @deprecated use {@link #CrateDBContainer(DockerImageName)} or {@link #CrateDBContainer(String)} instead + */ + @Deprecated + public CrateDBContainer() { + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); + } + + public CrateDBContainer(final String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public CrateDBContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + + this.waitStrategy = + Wait + .forHttp("/") + .forPort(4200) + .forStatusCode(200); + + addExposedPort(CRATEDB_PG_PORT); + addExposedPort(CRATEDB_HTTP_PORT); + } + + /** + * @return the ports on which to check if the container is ready + * @deprecated use {@link #getLivenessCheckPortNumbers()} instead + */ + @NotNull + @Override + @Deprecated + protected Set getLivenessCheckPorts() { + return super.getLivenessCheckPorts(); + } + + @Override + public String getDriverClassName() { + return "io.crate.client.jdbc.CrateDriver"; + } + + @Override + public String getJdbcUrl() { + String additionalUrlParams = constructUrlParameters("?", "&"); + return ( + "crate://" + + getHost() + + ":" + + getMappedPort(CRATEDB_PG_PORT) + + "/" + + databaseName + + additionalUrlParams + ); + } + + @Override + public String getDatabaseName() { + return databaseName; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getTestQueryString() { + return "SELECT 1"; + } + + @Override + public SELF withDatabaseName(final String databaseName) { + this.databaseName = databaseName; + return self(); + } + + @Override + public SELF withUsername(final String username) { + this.username = username; + return self(); + } + + @Override + public SELF withPassword(final String password) { + this.password = password; + return self(); + } + + @Override + protected void waitUntilContainerStarted() { + getWaitStrategy().waitUntilReady(this); + } + +} diff --git a/modules/cratedb/src/main/java/org/testcontainers/containers/CrateDBContainerProvider.java b/modules/cratedb/src/main/java/org/testcontainers/containers/CrateDBContainerProvider.java new file mode 100644 index 00000000000..2ccd6ceb8f2 --- /dev/null +++ b/modules/cratedb/src/main/java/org/testcontainers/containers/CrateDBContainerProvider.java @@ -0,0 +1,34 @@ +package org.testcontainers.containers; + +import org.testcontainers.jdbc.ConnectionUrl; +import org.testcontainers.utility.DockerImageName; + +/** + * Factory for CrateDB containers. + */ +public class CrateDBContainerProvider extends JdbcDatabaseContainerProvider { + + public static final String USER_PARAM = "user"; + + public static final String PASSWORD_PARAM = "password"; + + @Override + public boolean supports(String databaseType) { + return databaseType.equals(CrateDBContainer.NAME); + } + + @Override + public JdbcDatabaseContainer newInstance() { + return newInstance(CrateDBContainer.DEFAULT_TAG); + } + + @Override + public JdbcDatabaseContainer newInstance(String tag) { + return new CrateDBContainer(DockerImageName.parse(CrateDBContainer.IMAGE).withTag(tag)); + } + + @Override + public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) { + return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM); + } +} diff --git a/modules/cratedb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider b/modules/cratedb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider new file mode 100644 index 00000000000..e0a3c27a029 --- /dev/null +++ b/modules/cratedb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider @@ -0,0 +1 @@ +org.testcontainers.containers.CrateDBContainerProvider diff --git a/modules/cratedb/src/test/java/org/testcontainers/CrateDBTestImages.java b/modules/cratedb/src/test/java/org/testcontainers/CrateDBTestImages.java new file mode 100644 index 00000000000..363822c5dc0 --- /dev/null +++ b/modules/cratedb/src/test/java/org/testcontainers/CrateDBTestImages.java @@ -0,0 +1,7 @@ +package org.testcontainers; + +import org.testcontainers.utility.DockerImageName; + +public interface CrateDBTestImages { + DockerImageName CRATEDB_TEST_IMAGE = DockerImageName.parse("crate:5.2.3"); +} diff --git a/modules/cratedb/src/test/java/org/testcontainers/containers/CrateDBConnectionURLTest.java b/modules/cratedb/src/test/java/org/testcontainers/containers/CrateDBConnectionURLTest.java new file mode 100644 index 00000000000..ed0409942dd --- /dev/null +++ b/modules/cratedb/src/test/java/org/testcontainers/containers/CrateDBConnectionURLTest.java @@ -0,0 +1,84 @@ +package org.testcontainers.containers; + +import org.junit.Test; +import org.testcontainers.CrateDBTestImages; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +public class CrateDBConnectionURLTest { + + @Test + public void shouldCorrectlyAppendQueryString() { + CrateDBContainer cratedb = new FixedJdbcUrlCrateDBContainer(); + String connectionUrl = cratedb.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified"); + String queryString = connectionUrl.substring(connectionUrl.indexOf('?')); + + assertThat(queryString) + .as("Query String contains expected params") + .contains("?stringtype=unspecified&stringtype=unspecified"); + assertThat(queryString.indexOf('?')).as("Query String starts with '?'").isZero(); + assertThat(queryString.substring(1)).as("Query String does not contain extra '?'").doesNotContain("?"); + } + + @Test + public void shouldCorrectlyAppendQueryStringWhenNoBaseParams() { + CrateDBContainer cratedb = new NoParamsUrlCrateDBContainer(); + String connectionUrl = cratedb.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified"); + String queryString = connectionUrl.substring(connectionUrl.indexOf('?')); + + assertThat(queryString) + .as("Query String contains expected params") + .contains("?stringtype=unspecified&stringtype=unspecified"); + assertThat(queryString.indexOf('?')).as("Query String starts with '?'").isZero(); + assertThat(queryString.substring(1)).as("Query String does not contain extra '?'").doesNotContain("?"); + } + + @Test + public void shouldReturnOriginalURLWhenEmptyQueryString() { + CrateDBContainer cratedb = new FixedJdbcUrlCrateDBContainer(); + String connectionUrl = cratedb.constructUrlForConnection(""); + + assertThat(cratedb.getJdbcUrl()).as("Query String remains unchanged").isEqualTo(connectionUrl); + } + + @Test + public void shouldRejectInvalidQueryString() { + assertThat( + catchThrowable(() -> { + new NoParamsUrlCrateDBContainer().constructUrlForConnection("stringtype=unspecified"); + }) + ) + .as("Fails when invalid query string provided") + .isInstanceOf(IllegalArgumentException.class); + } + + static class FixedJdbcUrlCrateDBContainer extends CrateDBContainer { + + public FixedJdbcUrlCrateDBContainer() { + super(CrateDBTestImages.CRATEDB_TEST_IMAGE); + } + + @Override + public String getHost() { + return "localhost"; + } + + @Override + public Integer getMappedPort(int originalPort) { + return 5432; + } + } + + static class NoParamsUrlCrateDBContainer extends CrateDBContainer { + + public NoParamsUrlCrateDBContainer() { + super(CrateDBTestImages.CRATEDB_TEST_IMAGE); + } + + @Override + public String getJdbcUrl() { + return "crate://host:port/database"; + } + } +} diff --git a/modules/cratedb/src/test/java/org/testcontainers/junit/cratedb/SimpleCrateDBTest.java b/modules/cratedb/src/test/java/org/testcontainers/junit/cratedb/SimpleCrateDBTest.java new file mode 100644 index 00000000000..84c893c2776 --- /dev/null +++ b/modules/cratedb/src/test/java/org/testcontainers/junit/cratedb/SimpleCrateDBTest.java @@ -0,0 +1,69 @@ +package org.testcontainers.junit.cratedb; + +import org.junit.Test; +import org.testcontainers.CrateDBTestImages; +import org.testcontainers.containers.CrateDBContainer; +import org.testcontainers.db.AbstractContainerDatabaseTest; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.LogManager; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SimpleCrateDBTest extends AbstractContainerDatabaseTest { + static { + // Postgres JDBC driver uses JUL; disable it to avoid annoying, irrelevant, stderr logs during connection testing + LogManager.getLogManager().getLogger("").setLevel(Level.OFF); + } + + @Test + public void testSimple() throws SQLException { + try (CrateDBContainer cratedb = new CrateDBContainer<>(CrateDBTestImages.CRATEDB_TEST_IMAGE)) { + cratedb.start(); + + ResultSet resultSet = performQuery(cratedb, "SELECT 1"); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); + assertHasCorrectExposedAndLivenessCheckPorts(cratedb); + } + } + + @Test + public void testCommandOverride() throws SQLException { + try ( + CrateDBContainer cratedb = new CrateDBContainer<>(CrateDBTestImages.CRATEDB_TEST_IMAGE) + .withCommand("crate -C cluster.name=testcontainers") + ) { + cratedb.start(); + + ResultSet resultSet = performQuery(cratedb, "select name from sys.cluster"); + String result = resultSet.getString(1); + assertThat(result).as("cluster name should be overriden").isEqualTo("testcontainers"); + } + } + + @Test + public void testExplicitInitScript() throws SQLException { + try ( + CrateDBContainer cratedb = new CrateDBContainer<>(CrateDBTestImages.CRATEDB_TEST_IMAGE) + .withInitScript("somepath/init_cratedb.sql") + ) { + cratedb.start(); + + ResultSet resultSet = performQuery(cratedb, "SELECT foo FROM bar"); + + String firstColumnValue = resultSet.getString(1); + assertThat(firstColumnValue).as("Value from init script should equal real value").isEqualTo("hello world"); + } + } + + private void assertHasCorrectExposedAndLivenessCheckPorts(CrateDBContainer cratedb) { + assertThat(cratedb.getExposedPorts()).containsExactly(CrateDBContainer.CRATEDB_PG_PORT, CrateDBContainer.CRATEDB_HTTP_PORT); + assertThat(cratedb.getLivenessCheckPortNumbers()) + .containsExactlyInAnyOrder( + cratedb.getMappedPort(CrateDBContainer.CRATEDB_PG_PORT), + cratedb.getMappedPort(CrateDBContainer.CRATEDB_HTTP_PORT)); + } +} diff --git a/modules/cratedb/src/test/resources/logback-test.xml b/modules/cratedb/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/cratedb/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + diff --git a/modules/cratedb/src/test/resources/somepath/init_cratedb.sql b/modules/cratedb/src/test/resources/somepath/init_cratedb.sql new file mode 100644 index 00000000000..4faa1ede65c --- /dev/null +++ b/modules/cratedb/src/test/resources/somepath/init_cratedb.sql @@ -0,0 +1,6 @@ +CREATE TABLE bar ( + foo STRING +); + +INSERT INTO bar (foo) VALUES ('hello world'); +REFRESH TABLE bar;