diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index c31dd05e048..33a909afd22 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -27,6 +27,7 @@ body: - DB2 - Dynalite - Elasticsearch + - GaussDB - GCloud - Grafana - HiveMQ diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml index 9b9a06ecf6a..5ca2d16c037 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -27,6 +27,7 @@ body: - DB2 - Dynalite - Elasticsearch + - GaussDB - GCloud - Grafana - HiveMQ diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index b655b4ac505..61ebd39621e 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -27,6 +27,7 @@ body: - DB2 - Dynalite - Elasticsearch + - GaussDB - GCloud - Grafana - HiveMQ diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fe7a57a8603..a8b4670ae52 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -105,6 +105,11 @@ updates: schedule: interval: "monthly" open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/modules/gaussdb" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/gcloud" schedule: diff --git a/.github/labeler.yml b/.github/labeler.yml index f4649bd7f99..a47c4aa36d3 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -71,6 +71,10 @@ - changed-files: - any-glob-to-any-file: - modules/elasticsearch/**/* +"modules/gaussdb": + - changed-files: + - any-glob-to-any-file: + - modules/gaussdb/**/* "modules/gcloud": - changed-files: - any-glob-to-any-file: diff --git a/docs/modules/databases/gaussdb.md b/docs/modules/databases/gaussdb.md new file mode 100644 index 00000000000..5b066963157 --- /dev/null +++ b/docs/modules/databases/gaussdb.md @@ -0,0 +1,33 @@ +# GaussDB Module + +See [Database containers](./index.md) for documentation and usage that is common to all relational database container types. + +## Usage example + +You can use 'GaussDBContainer' like any other JDBC container: + +[Container creation](../../../modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java) inside_block:constructor + + +## Adding this module to your project dependencies + +Add the following dependency to your `pom.xml`/`build.gradle` file: + +=== "Gradle" + ```groovy + testImplementation "org.testcontainers:gaussdb:{{latest_version}}" + ``` +=== "Maven" + ```xml + + org.testcontainers + gaussdb + {{latest_version}} + test + + ``` + +!!! hint + Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency. + + diff --git a/modules/gaussdb/build.gradle b/modules/gaussdb/build.gradle new file mode 100644 index 00000000000..5c72a4dd20a --- /dev/null +++ b/modules/gaussdb/build.gradle @@ -0,0 +1,10 @@ +description = "Testcontainers :: JDBC :: GaussDB" + +dependencies { + api project(':jdbc') + + testImplementation project(':jdbc-test') + testRuntimeOnly 'com.huaweicloud.gaussdb:gaussdbjdbc:506.0.0.b058' + + compileOnly 'org.jetbrains:annotations:24.1.0' +} diff --git a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java new file mode 100644 index 00000000000..78a2e88cd7e --- /dev/null +++ b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainer.java @@ -0,0 +1,169 @@ +package org.testcontainers.containers; + +import org.jetbrains.annotations.NotNull; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.containers.wait.strategy.WaitStrategy; +import org.testcontainers.containers.wait.strategy.WaitStrategyTarget; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +/** + * Testcontainers implementation for GaussDB. + *

+ * Supported images: {@code opengauss/opengauss} + *

+ * Exposed ports: 8000 + */ +public class GaussDBContainer> extends JdbcDatabaseContainer { + + public static final String NAME = "gaussdb"; + + public static final String IMAGE = "opengauss/opengauss"; + + public static final String DEFAULT_TAG = "7.0.0-RC1.B023"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(IMAGE); + + public static final Integer GaussDB_PORT = 8000; + + public static final String DEFAULT_USER_NAME = "test"; + + // At least one uppercase, lowercase, numeric, special character, and password length(8). + public static final String DEFAULT_PASSWORD = "Test@123"; + + private String databaseName = "gaussdb"; + + private String username = DEFAULT_USER_NAME; + + private String password = DEFAULT_PASSWORD; + + private static final String PASSWORD_REGEX = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!*(),.?\":{}|<>]).{8,}$"; + + private static final Pattern PASSWORD_PATTERN = Pattern.compile(PASSWORD_REGEX); + + /** + * @deprecated use {@link #GaussDBContainer(DockerImageName)} or {@link #GaussDBContainer(String)} instead + */ + @Deprecated + public GaussDBContainer() { + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); + } + + public GaussDBContainer(final String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public GaussDBContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + setWaitStrategy(new WaitStrategy() { + @Override + public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) { + Wait.forListeningPort().waitUntilReady(waitStrategyTarget); + try { + // Open Gauss will set up users and password when ports are ready. + Wait.forLogMessage(".*gs_ctl stopped.*", 1).waitUntilReady(waitStrategyTarget); + // Not enough and no idea + TimeUnit.SECONDS.sleep(3); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Override + public WaitStrategy withStartupTimeout(Duration duration) { + return GenericContainer.DEFAULT_WAIT_STRATEGY.withStartupTimeout(duration); + } + }); + } + + /** + * @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 + protected void configure() { + // Disable GaussDB driver use of java.util.logging to reduce noise at startup time + withUrlParam("loggerLevel", "OFF"); + addExposedPorts(GaussDB_PORT); + addEnv("GS_DB", databaseName); + addEnv("GS_PORT", String.valueOf(GaussDB_PORT)); + addEnv("GS_USERNAME", username); + addEnv("GS_PASSWORD", password); + } + + @Override + public String getDriverClassName() { + return "com.huawei.gaussdb.jdbc.Driver"; + } + + @Override + public String getJdbcUrl() { + String additionalUrlParams = constructUrlParameters("?", "&"); + return ( + "jdbc:gaussdb://" + + getHost() + + ":" + + getMappedPort(GaussDB_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) { + if (!PASSWORD_PATTERN.matcher(password).matches()){ + throw new ContainerLaunchException("The password should contain at least one uppercase, lowercase, numeric, special character, and password length(8)."); + } + this.password = password; + return self(); + } + + @Override + protected void waitUntilContainerStarted() { + getWaitStrategy().waitUntilReady(this); + } +} diff --git a/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java new file mode 100644 index 00000000000..e21aebb85ec --- /dev/null +++ b/modules/gaussdb/src/main/java/org/testcontainers/containers/GaussDBContainerProvider.java @@ -0,0 +1,34 @@ +package org.testcontainers.containers; + +import org.testcontainers.jdbc.ConnectionUrl; +import org.testcontainers.utility.DockerImageName; + +/** + * Factory for GaussDB containers. + */ +public class GaussDBContainerProvider 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(GaussDBContainer.NAME); + } + + @Override + public JdbcDatabaseContainer newInstance() { + return newInstance(GaussDBContainer.DEFAULT_TAG); + } + + @Override + public JdbcDatabaseContainer newInstance(String tag) { + return new GaussDBContainer(DockerImageName.parse(GaussDBContainer.IMAGE).withTag(tag)); + } + + @Override + public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) { + return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM); + } +} diff --git a/modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider b/modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider new file mode 100644 index 00000000000..9707299c2ba --- /dev/null +++ b/modules/gaussdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider @@ -0,0 +1 @@ +org.testcontainers.containers.GaussDBContainerProvider diff --git a/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java b/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java new file mode 100644 index 00000000000..25d0975fd60 --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/GaussDBTestImages.java @@ -0,0 +1,7 @@ +package org.testcontainers; + +import org.testcontainers.utility.DockerImageName; + +public interface GaussDBTestImages { + DockerImageName GAUSSDB_TEST_IMAGE = DockerImageName.parse("opengauss/opengauss:7.0.0-RC1.B023"); +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java b/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java new file mode 100644 index 00000000000..31a6f93f189 --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/containers/GaussDBConnectionURLTest.java @@ -0,0 +1,84 @@ +package org.testcontainers.containers; + +import org.junit.Test; +import org.testcontainers.GaussDBTestImages; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +public class GaussDBConnectionURLTest { + + @Test + public void shouldCorrectlyAppendQueryString() { + GaussDBContainer gaussDB = new FixedJdbcUrlGaussDBContainer(); + String connectionUrl = gaussDB.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() { + GaussDBContainer gaussDB = new NoParamsUrlGaussDBContainer(); + String connectionUrl = gaussDB.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() { + GaussDBContainer gaussDB = new FixedJdbcUrlGaussDBContainer(); + String connectionUrl = gaussDB.constructUrlForConnection(""); + + assertThat(gaussDB.getJdbcUrl()).as("Query String remains unchanged").isEqualTo(connectionUrl); + } + + @Test + public void shouldRejectInvalidQueryString() { + assertThat( + catchThrowable(() -> { + new NoParamsUrlGaussDBContainer().constructUrlForConnection("stringtype=unspecified"); + }) + ) + .as("Fails when invalid query string provided") + .isInstanceOf(IllegalArgumentException.class); + } + + static class FixedJdbcUrlGaussDBContainer extends GaussDBContainer { + + public FixedJdbcUrlGaussDBContainer() { + super(GaussDBTestImages.GAUSSDB_TEST_IMAGE); + } + + @Override + public String getHost() { + return "localhost"; + } + + @Override + public Integer getMappedPort(int originalPort) { + return 34532; + } + } + + static class NoParamsUrlGaussDBContainer extends GaussDBContainer { + + public NoParamsUrlGaussDBContainer() { + super(GaussDBTestImages.GAUSSDB_TEST_IMAGE); + } + + @Override + public String getJdbcUrl() { + return "jdbc:gaussdb://host:port/database"; + } + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java new file mode 100644 index 00000000000..ec3efa85603 --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java @@ -0,0 +1,52 @@ +package org.testcontainers.jdbc; + +import org.junit.AfterClass; +import org.junit.Test; +import org.testcontainers.containers.JdbcDatabaseContainer; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}. + * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:gaussdb) URL forces it to live here in + * the mysql module, to avoid circular dependencies. + * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test. + */ +public class DatabaseDriverShutdownTest { + + @AfterClass + public static void testCleanup() { + ContainerDatabaseDriver.killContainers(); + } + + @Test + public void shouldStopContainerWhenAllConnectionsClosed() throws SQLException { + final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename"; + + getConnectionAndClose(jdbcUrl); + + JdbcDatabaseContainer container = ContainerDatabaseDriver.getContainer(jdbcUrl); + assertThat(container).as("Database container instance is null as expected").isNull(); + } + + @Test + public void shouldNotStopDaemonContainerWhenAllConnectionsClosed() throws SQLException { + final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename?TC_DAEMON=true"; + + getConnectionAndClose(jdbcUrl); + + JdbcDatabaseContainer container = ContainerDatabaseDriver.getContainer(jdbcUrl); + assertThat(container).as("Database container instance is not null as expected").isNotNull(); + assertThat(container.isRunning()).as("Database container is running as expected").isTrue(); + } + + private void getConnectionAndClose(String jdbcUrl) throws SQLException { + try (Connection connection = DriverManager.getConnection(jdbcUrl)) { + assertThat(connection).as("Obtained connection as expected").isNotNull(); + } + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java new file mode 100644 index 00000000000..00c40a4456c --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java @@ -0,0 +1,37 @@ +package org.testcontainers.jdbc; + +import org.junit.Test; +import org.testcontainers.containers.Container; +import org.testcontainers.containers.JdbcDatabaseContainer; + +import java.sql.Connection; +import java.sql.DriverManager; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}. + * However, the need to use the {@link org.testcontainers.containers.GaussDBContainerProvider} (due to the jdbc:tc:gaussdb) URL forces it to live here in + * the mysql module, to avoid circular dependencies. + * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test. + */ +public class DatabaseDriverTmpfsTest { + + @Test + public void testDatabaseHasTmpFsViaConnectionString() throws Exception { + final String jdbcUrl = "jdbc:tc:gaussdb://hostname/databasename?TC_TMPFS=/testtmpfs:rw"; + try (Connection ignored = DriverManager.getConnection(jdbcUrl)) { + JdbcDatabaseContainer container = ContainerDatabaseDriver.getContainer(jdbcUrl); + // check file doesn't exist + String path = "/testtmpfs/test.file"; + Container.ExecResult execResult = container.execInContainer("ls", path); + assertThat(execResult.getExitCode()) + .as("tmpfs inside container doesn't have file that doesn't exist") + .isNotZero(); + // touch && check file does exist + container.execInContainer("touch", path); + execResult = container.execInContainer("ls", path); + assertThat(execResult.getExitCode()).as("tmpfs inside container has file that does exist").isZero(); + } + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java new file mode 100644 index 00000000000..4932fdcc4c8 --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/jdbc/gaussdb/GaussDBJDBCDriverTest.java @@ -0,0 +1,24 @@ +package org.testcontainers.jdbc.gaussdb; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.testcontainers.jdbc.AbstractJDBCDriverTest; + +import java.util.Arrays; +import java.util.EnumSet; + +@RunWith(Parameterized.class) +public class GaussDBJDBCDriverTest extends AbstractJDBCDriverTest { + + @Parameterized.Parameters(name = "{index} - {0}") + public static Iterable data() { + return Arrays.asList( + new Object[][] { + { + "jdbc:tc:gaussdb://hostname/databasename?user=someuser&password=Enmo@123", + EnumSet.of(Options.JDBCParams), + }, + } + ); + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java new file mode 100644 index 00000000000..5425ee7b659 --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/CustomizableGaussDBTest.java @@ -0,0 +1,38 @@ +package org.testcontainers.junit.gaussdb; + +import org.junit.Test; +import org.testcontainers.GaussDBTestImages; +import org.testcontainers.containers.GaussDBContainer; +import org.testcontainers.db.AbstractContainerDatabaseTest; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CustomizableGaussDBTest extends AbstractContainerDatabaseTest { + + private static final String DB_NAME = "foo"; + + private static final String USER = "bar"; + + // At least one uppercase, lowercase, numeric, special character, and password length(8). + private static final String PWD = "GaussDB@123"; + + @Test + public void testSimple() throws SQLException { + try ( + GaussDBContainer gaussDBContainer = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withDatabaseName(DB_NAME) + .withUsername(USER) + .withPassword(PWD) + ) { + gaussDBContainer.start(); + + ResultSet resultSet = performQuery(gaussDBContainer, "SELECT 1"); + + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); + } + } +} diff --git a/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java new file mode 100644 index 00000000000..6a1f649ac20 --- /dev/null +++ b/modules/gaussdb/src/test/java/org/testcontainers/junit/gaussdb/SimpleGaussDBTest.java @@ -0,0 +1,128 @@ +package org.testcontainers.junit.gaussdb; + +import org.junit.Test; +import org.testcontainers.GaussDBTestImages; +import org.testcontainers.containers.GaussDBContainer; +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; +import static org.assertj.core.api.Assertions.assertThatNoException; + +public class SimpleGaussDBTest extends AbstractContainerDatabaseTest { + static { + // Gaussdb 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 (GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE)) { + gaussdb.start(); + + ResultSet resultSet = performQuery(gaussdb, "SELECT 1"); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); + assertHasCorrectExposedAndLivenessCheckPorts(gaussdb); + } + } + + @Test + public void testCommandOverride() throws SQLException { + try ( + GaussDBContainer gauss = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withCommand("gaussdb", "-N", "42") + ) { + gauss.start(); + + ResultSet resultSet = performQuery(gauss, "SELECT current_setting('max_connections')"); + String result = resultSet.getString(1); + assertThat(result).as("max_connections should be overridden").isEqualTo("42"); + } + } + + @Test + public void testUnsetCommand() throws SQLException { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withCommand("gaussdb", "-N", "42") + .withCommand() + ) { + gaussdb.start(); + + ResultSet resultSet = performQuery(gaussdb, "SELECT current_setting('max_connections')"); + String result = resultSet.getString(1); + assertThat(result).as("max_connections should not be overridden").isNotEqualTo("42"); + } + } + + @Test + public void testMissingInitScript() { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withInitScript(null) + ) { + assertThatNoException().isThrownBy(gaussdb::start); + } + } + + @Test + public void testExplicitInitScript() throws SQLException { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withInitScript("somepath/init_gaussdb.sql") + ) { + gaussdb.start(); + + ResultSet resultSet = performQuery(gaussdb, "SELECT foo FROM bar"); + + String firstColumnValue = resultSet.getString(1); + assertThat(firstColumnValue).as("Value from init script should equal real value").isEqualTo("hello world"); + } + } + + @Test + public void testExplicitInitScripts() throws SQLException { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withInitScripts("somepath/init_gaussdb.sql", "somepath/init_gaussdb_2.sql") + ) { + gaussdb.start(); + + ResultSet resultSet = performQuery( + gaussdb, + "SELECT foo AS value FROM bar UNION SELECT bar AS value FROM foo" + ); + + String columnValue1 = resultSet.getString(1); + resultSet.next(); + String columnValue2 = resultSet.getString(1); + assertThat(columnValue1).as("Value from init script 1 should equal real value").isEqualTo("hello world"); + assertThat(columnValue2).as("Value from init script 2 should equal real value").isEqualTo("hello world 2"); + } + } + + @Test + public void testWithAdditionalUrlParamInJdbcUrl() { + try ( + GaussDBContainer gaussdb = new GaussDBContainer<>(GaussDBTestImages.GAUSSDB_TEST_IMAGE) + .withUrlParam("charSet", "UNICODE") + ) { + gaussdb.start(); + String jdbcUrl = gaussdb.getJdbcUrl(); + assertThat(jdbcUrl).contains("?"); + assertThat(jdbcUrl).contains("&"); + assertThat(jdbcUrl).contains("charSet=UNICODE"); + } + } + + private void assertHasCorrectExposedAndLivenessCheckPorts(GaussDBContainer gaussdb) { + assertThat(gaussdb.getExposedPorts()).containsExactly(GaussDBContainer.GaussDB_PORT); + assertThat(gaussdb.getLivenessCheckPortNumbers()) + .containsExactly(gaussdb.getMappedPort(GaussDBContainer.GaussDB_PORT)); + } +} diff --git a/modules/gaussdb/src/test/resources/logback-test.xml b/modules/gaussdb/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/gaussdb/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + diff --git a/modules/gaussdb/src/test/resources/somepath/init_gaussdb.sql b/modules/gaussdb/src/test/resources/somepath/init_gaussdb.sql new file mode 100644 index 00000000000..2b00ee968b0 --- /dev/null +++ b/modules/gaussdb/src/test/resources/somepath/init_gaussdb.sql @@ -0,0 +1,5 @@ +CREATE TABLE bar ( + foo VARCHAR(255) +); + +INSERT INTO bar (foo) VALUES ('hello world'); \ No newline at end of file diff --git a/modules/gaussdb/src/test/resources/somepath/init_gaussdb_2.sql b/modules/gaussdb/src/test/resources/somepath/init_gaussdb_2.sql new file mode 100644 index 00000000000..f4ecf9bbfad --- /dev/null +++ b/modules/gaussdb/src/test/resources/somepath/init_gaussdb_2.sql @@ -0,0 +1,5 @@ +CREATE TABLE foo ( + bar VARCHAR(255) +); + +INSERT INTO foo (bar) VALUES ('hello world 2'); diff --git a/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java b/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java index 5a22e37ecfc..49c06c49c24 100644 --- a/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java +++ b/modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java @@ -131,7 +131,8 @@ private void performTestForJDBCParamUsage(HikariDataSource dataSource) throws SQ databaseType.equalsIgnoreCase("postgresql") || databaseType.equalsIgnoreCase("postgis") || databaseType.equalsIgnoreCase("timescaledb") || - databaseType.equalsIgnoreCase("pgvector") + databaseType.equalsIgnoreCase("pgvector") || + databaseType.equalsIgnoreCase("gaussdb") ) { databaseQuery = "SELECT CURRENT_DATABASE()"; } diff --git a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainerProvider.java b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainerProvider.java index 3cd74dc98d4..54f1905b180 100644 --- a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainerProvider.java +++ b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainerProvider.java @@ -66,7 +66,13 @@ protected JdbcDatabaseContainer newInstanceFromConnectionUrl( final String databaseName = connectionUrl.getDatabaseName().orElse("test"); final String user = connectionUrl.getQueryParameters().getOrDefault(userParamName, "test"); - final String password = connectionUrl.getQueryParameters().getOrDefault(pwdParamName, "test"); + final String password; + if ("gaussdb".equalsIgnoreCase(connectionUrl.getDatabaseType())){ + // At least one uppercase, lowercase, numeric, special character, and password length(8). + password = connectionUrl.getQueryParameters().getOrDefault(pwdParamName, "Test@123"); + }else { + password = connectionUrl.getQueryParameters().getOrDefault(pwdParamName, "test"); + } final JdbcDatabaseContainer instance; if (connectionUrl.getImageTag().isPresent()) {