diff --git a/docs/modules/databases/mssqlserver.md b/docs/modules/databases/mssqlserver.md index f12365aaa18..bde933b1e72 100644 --- a/docs/modules/databases/mssqlserver.md +++ b/docs/modules/databases/mssqlserver.md @@ -7,7 +7,7 @@ Testcontainers module for [MS SQL Server](https://mcr.microsoft.com/en-us/artifa You can start a MS SQL Server container instance from any Java application by using: -[Container definition](../../../modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java) inside_block:container +[Container definition](../../../modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/MSSQLServerContainerTest.java) inside_block:container !!! warning "EULA Acceptance" diff --git a/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java b/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java index 10ccbff2c36..a30de6549ba 100644 --- a/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java +++ b/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java @@ -13,7 +13,10 @@ * Supported image: {@code mcr.microsoft.com/mssql/server} *

* Exposed ports: 1433 + * + * @deprecated use {@link org.testcontainers.mssqlserver.MSSQLServerContainer} instead. */ +@Deprecated public class MSSQLServerContainer> extends JdbcDatabaseContainer { private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mcr.microsoft.com/mssql/server"); diff --git a/modules/mssqlserver/src/main/java/org/testcontainers/mssqlserver/MSSQLServerContainer.java b/modules/mssqlserver/src/main/java/org/testcontainers/mssqlserver/MSSQLServerContainer.java new file mode 100644 index 00000000000..6ad74ea7e72 --- /dev/null +++ b/modules/mssqlserver/src/main/java/org/testcontainers/mssqlserver/MSSQLServerContainer.java @@ -0,0 +1,156 @@ +package org.testcontainers.mssqlserver; + +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.LicenseAcceptance; + +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** + * Testcontainers implementation for Microsoft SQL Server. + *

+ * Supported image: {@code mcr.microsoft.com/mssql/server} + *

+ * Exposed ports: 1433 + */ +public class MSSQLServerContainer extends JdbcDatabaseContainer { + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mcr.microsoft.com/mssql/server"); + + public static final String NAME = "sqlserver"; + + public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart(); + + public static final Integer MS_SQL_SERVER_PORT = 1433; + + static final String DEFAULT_USER = "sa"; + + static final String DEFAULT_PASSWORD = "A_Str0ng_Required_Password"; + + private String password = DEFAULT_PASSWORD; + + private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 240; + + private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 240; + + private static final Pattern[] PASSWORD_CATEGORY_VALIDATION_PATTERNS = new Pattern[] { + Pattern.compile("[A-Z]+"), + Pattern.compile("[a-z]+"), + Pattern.compile("[0-9]+"), + Pattern.compile("[^a-zA-Z0-9]+", Pattern.CASE_INSENSITIVE), + }; + + public MSSQLServerContainer(final String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public MSSQLServerContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + + withStartupTimeoutSeconds(DEFAULT_STARTUP_TIMEOUT_SECONDS); + withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS); + addExposedPort(MS_SQL_SERVER_PORT); + } + + @Override + public Set getLivenessCheckPortNumbers() { + return super.getLivenessCheckPortNumbers(); + } + + @Override + protected void configure() { + // If license was not accepted programmatically, check if it was accepted via resource file + if (!getEnvMap().containsKey("ACCEPT_EULA")) { + LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName()); + acceptLicense(); + } + + addEnv("MSSQL_SA_PASSWORD", password); + } + + /** + * Accepts the license for the SQLServer container by setting the ACCEPT_EULA=Y + * variable as described at https://hub.docker.com/_/microsoft-mssql-server + */ + public MSSQLServerContainer acceptLicense() { + addEnv("ACCEPT_EULA", "Y"); + return self(); + } + + @Override + public String getDriverClassName() { + return "com.microsoft.sqlserver.jdbc.SQLServerDriver"; + } + + @Override + protected String constructUrlForConnection(String queryString) { + // The JDBC driver of MS SQL Server enables encryption by default for versions > 10.1.0. + // We need to disable it by default to be able to use the container without having to pass extra params. + // See https://github.com/microsoft/mssql-jdbc/releases/tag/v10.1.0 + if (urlParameters.keySet().stream().map(String::toLowerCase).noneMatch("encrypt"::equals)) { + urlParameters.put("encrypt", "false"); + } + return super.constructUrlForConnection(queryString); + } + + @Override + public String getJdbcUrl() { + String additionalUrlParams = constructUrlParameters(";", ";"); + return "jdbc:sqlserver://" + getHost() + ":" + getMappedPort(MS_SQL_SERVER_PORT) + additionalUrlParams; + } + + @Override + public String getUsername() { + return DEFAULT_USER; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getTestQueryString() { + return "SELECT 1"; + } + + @Override + public MSSQLServerContainer withPassword(final String password) { + checkPasswordStrength(password); + this.password = password; + return self(); + } + + private void checkPasswordStrength(String password) { + if (password == null) { + throw new IllegalArgumentException("Null password is not allowed"); + } + + if (password.length() < 8) { + throw new IllegalArgumentException("Password should be at least 8 characters long"); + } + + if (password.length() > 128) { + throw new IllegalArgumentException("Password can be up to 128 characters long"); + } + + long satisfiedCategories = Stream + .of(PASSWORD_CATEGORY_VALIDATION_PATTERNS) + .filter(p -> p.matcher(password).find()) + .count(); + + if (satisfiedCategories < 3) { + throw new IllegalArgumentException( + "Password must contain characters from three of the following four categories:\n" + + " - Latin uppercase letters (A through Z)\n" + + " - Latin lowercase letters (a through z)\n" + + " - Base 10 digits (0 through 9)\n" + + " - Non-alphanumeric characters such as: exclamation point (!), dollar sign ($), number sign (#), " + + "or percent (%)." + ); + } + } +} diff --git a/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/CustomizableMSSQLServerTest.java b/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/CustomizableMSSQLServerTest.java deleted file mode 100644 index 630b6c5356c..00000000000 --- a/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/CustomizableMSSQLServerTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.testcontainers.junit.mssqlserver; - -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.MSSQLServerContainer; -import org.testcontainers.db.AbstractContainerDatabaseTest; -import org.testcontainers.utility.DockerImageName; - -import java.sql.ResultSet; -import java.sql.SQLException; - -import static org.assertj.core.api.Assertions.assertThat; - -class CustomizableMSSQLServerTest extends AbstractContainerDatabaseTest { - - private static final String STRONG_PASSWORD = "myStrong(!)Password"; - - @Test - void testSqlServerConnection() throws SQLException { - try ( - MSSQLServerContainer mssqlServerContainer = new MSSQLServerContainer<>( - DockerImageName.parse("mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04") - ) - .withPassword(STRONG_PASSWORD) - ) { - mssqlServerContainer.start(); - - ResultSet resultSet = performQuery(mssqlServerContainer, mssqlServerContainer.getTestQueryString()); - int resultSetInt = resultSet.getInt(1); - assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); - } - } -} diff --git a/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/CustomPasswordMSSQLServerTest.java b/modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/CustomPasswordMSSQLServerTest.java similarity index 98% rename from modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/CustomPasswordMSSQLServerTest.java rename to modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/CustomPasswordMSSQLServerTest.java index 06aa1dc4a61..faaaabd7d01 100644 --- a/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/CustomPasswordMSSQLServerTest.java +++ b/modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/CustomPasswordMSSQLServerTest.java @@ -1,4 +1,4 @@ -package org.testcontainers.junit.mssqlserver; +package org.testcontainers.mssqlserver; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.params.ParameterizedTest; diff --git a/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java b/modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/MSSQLServerContainerTest.java similarity index 70% rename from modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java rename to modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/MSSQLServerContainerTest.java index 0d995911893..52269f11b9e 100644 --- a/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java +++ b/modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/MSSQLServerContainerTest.java @@ -1,9 +1,9 @@ -package org.testcontainers.junit.mssqlserver; +package org.testcontainers.mssqlserver; import org.junit.jupiter.api.Test; import org.testcontainers.MSSQLServerTestImages; -import org.testcontainers.containers.MSSQLServerContainer; import org.testcontainers.db.AbstractContainerDatabaseTest; +import org.testcontainers.utility.DockerImageName; import java.sql.ResultSet; import java.sql.SQLException; @@ -13,12 +13,12 @@ import static org.assertj.core.api.Assertions.assertThat; -class SimpleMSSQLServerTest extends AbstractContainerDatabaseTest { +class MSSQLServerContainerTest extends AbstractContainerDatabaseTest { @Test void testSimple() throws SQLException { try ( // container { - MSSQLServerContainer mssqlServer = new MSSQLServerContainer<>( + MSSQLServerContainer mssqlServer = new MSSQLServerContainer( "mcr.microsoft.com/mssql/server:2022-CU20-ubuntu-22.04" ) .acceptLicense() @@ -36,7 +36,7 @@ void testSimple() throws SQLException { @Test void testWithAdditionalUrlParamInJdbcUrl() { try ( - MSSQLServerContainer mssqlServer = new MSSQLServerContainer<>(MSSQLServerTestImages.MSSQL_SERVER_IMAGE) + MSSQLServerContainer mssqlServer = new MSSQLServerContainer(MSSQLServerTestImages.MSSQL_SERVER_IMAGE) .withUrlParam("integratedSecurity", "false") .withUrlParam("applicationName", "MyApp") ) { @@ -49,9 +49,7 @@ void testWithAdditionalUrlParamInJdbcUrl() { @Test void testSetupDatabase() throws SQLException { - try ( - MSSQLServerContainer mssqlServer = new MSSQLServerContainer<>(MSSQLServerTestImages.MSSQL_SERVER_IMAGE) - ) { + try (MSSQLServerContainer mssqlServer = new MSSQLServerContainer(MSSQLServerTestImages.MSSQL_SERVER_IMAGE)) { mssqlServer.start(); DataSource ds = getDataSource(mssqlServer); Statement statement = ds.getConnection().createStatement(); @@ -70,7 +68,23 @@ void testSetupDatabase() throws SQLException { } } - private void assertHasCorrectExposedAndLivenessCheckPorts(MSSQLServerContainer mssqlServer) { + @Test + void testSqlServerConnection() throws SQLException { + try ( + MSSQLServerContainer mssqlServerContainer = new MSSQLServerContainer( + DockerImageName.parse("mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04") + ) + .withPassword("myStrong(!)Password") + ) { + mssqlServerContainer.start(); + + ResultSet resultSet = performQuery(mssqlServerContainer, mssqlServerContainer.getTestQueryString()); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); + } + } + + private void assertHasCorrectExposedAndLivenessCheckPorts(MSSQLServerContainer mssqlServer) { assertThat(mssqlServer.getExposedPorts()).containsExactly(MSSQLServerContainer.MS_SQL_SERVER_PORT); assertThat(mssqlServer.getLivenessCheckPortNumbers()) .containsExactly(mssqlServer.getMappedPort(MSSQLServerContainer.MS_SQL_SERVER_PORT));