Skip to content

Commit 8e508b9

Browse files
authored
Add MSSQLServer container implementation under org.testcontainers.mssqlserver (#11085)
1 parent 4cda084 commit 8e508b9

File tree

6 files changed

+184
-43
lines changed

6 files changed

+184
-43
lines changed

docs/modules/databases/mssqlserver.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Testcontainers module for [MS SQL Server](https://mcr.microsoft.com/en-us/artifa
77
You can start a MS SQL Server container instance from any Java application by using:
88

99
<!--codeinclude-->
10-
[Container definition](../../../modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java) inside_block:container
10+
[Container definition](../../../modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/MSSQLServerContainerTest.java) inside_block:container
1111
<!--/codeinclude-->
1212

1313
!!! warning "EULA Acceptance"

modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
* Supported image: {@code mcr.microsoft.com/mssql/server}
1414
* <p>
1515
* Exposed ports: 1433
16+
*
17+
* @deprecated use {@link org.testcontainers.mssqlserver.MSSQLServerContainer} instead.
1618
*/
19+
@Deprecated
1720
public class MSSQLServerContainer<SELF extends MSSQLServerContainer<SELF>> extends JdbcDatabaseContainer<SELF> {
1821

1922
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mcr.microsoft.com/mssql/server");
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package org.testcontainers.mssqlserver;
2+
3+
import org.testcontainers.containers.JdbcDatabaseContainer;
4+
import org.testcontainers.utility.DockerImageName;
5+
import org.testcontainers.utility.LicenseAcceptance;
6+
7+
import java.util.Set;
8+
import java.util.regex.Pattern;
9+
import java.util.stream.Stream;
10+
11+
/**
12+
* Testcontainers implementation for Microsoft SQL Server.
13+
* <p>
14+
* Supported image: {@code mcr.microsoft.com/mssql/server}
15+
* <p>
16+
* Exposed ports: 1433
17+
*/
18+
public class MSSQLServerContainer extends JdbcDatabaseContainer<MSSQLServerContainer> {
19+
20+
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mcr.microsoft.com/mssql/server");
21+
22+
public static final String NAME = "sqlserver";
23+
24+
public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();
25+
26+
public static final Integer MS_SQL_SERVER_PORT = 1433;
27+
28+
static final String DEFAULT_USER = "sa";
29+
30+
static final String DEFAULT_PASSWORD = "A_Str0ng_Required_Password";
31+
32+
private String password = DEFAULT_PASSWORD;
33+
34+
private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 240;
35+
36+
private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 240;
37+
38+
private static final Pattern[] PASSWORD_CATEGORY_VALIDATION_PATTERNS = new Pattern[] {
39+
Pattern.compile("[A-Z]+"),
40+
Pattern.compile("[a-z]+"),
41+
Pattern.compile("[0-9]+"),
42+
Pattern.compile("[^a-zA-Z0-9]+", Pattern.CASE_INSENSITIVE),
43+
};
44+
45+
public MSSQLServerContainer(final String dockerImageName) {
46+
this(DockerImageName.parse(dockerImageName));
47+
}
48+
49+
public MSSQLServerContainer(final DockerImageName dockerImageName) {
50+
super(dockerImageName);
51+
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
52+
53+
withStartupTimeoutSeconds(DEFAULT_STARTUP_TIMEOUT_SECONDS);
54+
withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS);
55+
addExposedPort(MS_SQL_SERVER_PORT);
56+
}
57+
58+
@Override
59+
public Set<Integer> getLivenessCheckPortNumbers() {
60+
return super.getLivenessCheckPortNumbers();
61+
}
62+
63+
@Override
64+
protected void configure() {
65+
// If license was not accepted programmatically, check if it was accepted via resource file
66+
if (!getEnvMap().containsKey("ACCEPT_EULA")) {
67+
LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName());
68+
acceptLicense();
69+
}
70+
71+
addEnv("MSSQL_SA_PASSWORD", password);
72+
}
73+
74+
/**
75+
* Accepts the license for the SQLServer container by setting the ACCEPT_EULA=Y
76+
* variable as described at <a href="https://hub.docker.com/_/microsoft-mssql-server">https://hub.docker.com/_/microsoft-mssql-server</a>
77+
*/
78+
public MSSQLServerContainer acceptLicense() {
79+
addEnv("ACCEPT_EULA", "Y");
80+
return self();
81+
}
82+
83+
@Override
84+
public String getDriverClassName() {
85+
return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
86+
}
87+
88+
@Override
89+
protected String constructUrlForConnection(String queryString) {
90+
// The JDBC driver of MS SQL Server enables encryption by default for versions > 10.1.0.
91+
// We need to disable it by default to be able to use the container without having to pass extra params.
92+
// See https://github.com/microsoft/mssql-jdbc/releases/tag/v10.1.0
93+
if (urlParameters.keySet().stream().map(String::toLowerCase).noneMatch("encrypt"::equals)) {
94+
urlParameters.put("encrypt", "false");
95+
}
96+
return super.constructUrlForConnection(queryString);
97+
}
98+
99+
@Override
100+
public String getJdbcUrl() {
101+
String additionalUrlParams = constructUrlParameters(";", ";");
102+
return "jdbc:sqlserver://" + getHost() + ":" + getMappedPort(MS_SQL_SERVER_PORT) + additionalUrlParams;
103+
}
104+
105+
@Override
106+
public String getUsername() {
107+
return DEFAULT_USER;
108+
}
109+
110+
@Override
111+
public String getPassword() {
112+
return password;
113+
}
114+
115+
@Override
116+
public String getTestQueryString() {
117+
return "SELECT 1";
118+
}
119+
120+
@Override
121+
public MSSQLServerContainer withPassword(final String password) {
122+
checkPasswordStrength(password);
123+
this.password = password;
124+
return self();
125+
}
126+
127+
private void checkPasswordStrength(String password) {
128+
if (password == null) {
129+
throw new IllegalArgumentException("Null password is not allowed");
130+
}
131+
132+
if (password.length() < 8) {
133+
throw new IllegalArgumentException("Password should be at least 8 characters long");
134+
}
135+
136+
if (password.length() > 128) {
137+
throw new IllegalArgumentException("Password can be up to 128 characters long");
138+
}
139+
140+
long satisfiedCategories = Stream
141+
.of(PASSWORD_CATEGORY_VALIDATION_PATTERNS)
142+
.filter(p -> p.matcher(password).find())
143+
.count();
144+
145+
if (satisfiedCategories < 3) {
146+
throw new IllegalArgumentException(
147+
"Password must contain characters from three of the following four categories:\n" +
148+
" - Latin uppercase letters (A through Z)\n" +
149+
" - Latin lowercase letters (a through z)\n" +
150+
" - Base 10 digits (0 through 9)\n" +
151+
" - Non-alphanumeric characters such as: exclamation point (!), dollar sign ($), number sign (#), " +
152+
"or percent (%)."
153+
);
154+
}
155+
}
156+
}

modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/CustomizableMSSQLServerTest.java

Lines changed: 0 additions & 32 deletions
This file was deleted.

modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/CustomPasswordMSSQLServerTest.java renamed to modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/CustomPasswordMSSQLServerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.testcontainers.junit.mssqlserver;
1+
package org.testcontainers.mssqlserver;
22

33
import org.apache.commons.lang3.RandomStringUtils;
44
import org.junit.jupiter.params.ParameterizedTest;

modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java renamed to modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/MSSQLServerContainerTest.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
package org.testcontainers.junit.mssqlserver;
1+
package org.testcontainers.mssqlserver;
22

33
import org.junit.jupiter.api.Test;
44
import org.testcontainers.MSSQLServerTestImages;
5-
import org.testcontainers.containers.MSSQLServerContainer;
65
import org.testcontainers.db.AbstractContainerDatabaseTest;
6+
import org.testcontainers.utility.DockerImageName;
77

88
import java.sql.ResultSet;
99
import java.sql.SQLException;
@@ -13,12 +13,12 @@
1313

1414
import static org.assertj.core.api.Assertions.assertThat;
1515

16-
class SimpleMSSQLServerTest extends AbstractContainerDatabaseTest {
16+
class MSSQLServerContainerTest extends AbstractContainerDatabaseTest {
1717

1818
@Test
1919
void testSimple() throws SQLException {
2020
try ( // container {
21-
MSSQLServerContainer<?> mssqlServer = new MSSQLServerContainer<>(
21+
MSSQLServerContainer mssqlServer = new MSSQLServerContainer(
2222
"mcr.microsoft.com/mssql/server:2022-CU20-ubuntu-22.04"
2323
)
2424
.acceptLicense()
@@ -36,7 +36,7 @@ void testSimple() throws SQLException {
3636
@Test
3737
void testWithAdditionalUrlParamInJdbcUrl() {
3838
try (
39-
MSSQLServerContainer<?> mssqlServer = new MSSQLServerContainer<>(MSSQLServerTestImages.MSSQL_SERVER_IMAGE)
39+
MSSQLServerContainer mssqlServer = new MSSQLServerContainer(MSSQLServerTestImages.MSSQL_SERVER_IMAGE)
4040
.withUrlParam("integratedSecurity", "false")
4141
.withUrlParam("applicationName", "MyApp")
4242
) {
@@ -49,9 +49,7 @@ void testWithAdditionalUrlParamInJdbcUrl() {
4949

5050
@Test
5151
void testSetupDatabase() throws SQLException {
52-
try (
53-
MSSQLServerContainer<?> mssqlServer = new MSSQLServerContainer<>(MSSQLServerTestImages.MSSQL_SERVER_IMAGE)
54-
) {
52+
try (MSSQLServerContainer mssqlServer = new MSSQLServerContainer(MSSQLServerTestImages.MSSQL_SERVER_IMAGE)) {
5553
mssqlServer.start();
5654
DataSource ds = getDataSource(mssqlServer);
5755
Statement statement = ds.getConnection().createStatement();
@@ -70,7 +68,23 @@ void testSetupDatabase() throws SQLException {
7068
}
7169
}
7270

73-
private void assertHasCorrectExposedAndLivenessCheckPorts(MSSQLServerContainer<?> mssqlServer) {
71+
@Test
72+
void testSqlServerConnection() throws SQLException {
73+
try (
74+
MSSQLServerContainer mssqlServerContainer = new MSSQLServerContainer(
75+
DockerImageName.parse("mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04")
76+
)
77+
.withPassword("myStrong(!)Password")
78+
) {
79+
mssqlServerContainer.start();
80+
81+
ResultSet resultSet = performQuery(mssqlServerContainer, mssqlServerContainer.getTestQueryString());
82+
int resultSetInt = resultSet.getInt(1);
83+
assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1);
84+
}
85+
}
86+
87+
private void assertHasCorrectExposedAndLivenessCheckPorts(MSSQLServerContainer mssqlServer) {
7488
assertThat(mssqlServer.getExposedPorts()).containsExactly(MSSQLServerContainer.MS_SQL_SERVER_PORT);
7589
assertThat(mssqlServer.getLivenessCheckPortNumbers())
7690
.containsExactly(mssqlServer.getMappedPort(MSSQLServerContainer.MS_SQL_SERVER_PORT));

0 commit comments

Comments
 (0)