diff --git a/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java b/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java index 2080c24283b..bb5ce250f70 100644 --- a/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java +++ b/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java @@ -58,6 +58,18 @@ public class OracleContainer extends JdbcDatabaseContainer { private String password = APP_USER_PASSWORD; + /** + * Password for Oracle system user (e.g. SYSTEM/SYS). Defaults to {@link #APP_USER_PASSWORD} + * for backwards compatibility, but can be customized independently via {@link #withOraclePassword(String)}. + */ + private String oraclePassword = APP_USER_PASSWORD; + + /** + * Tracks whether {@link #withOraclePassword(String)} was called to avoid overriding + * the system password when {@link #withPassword(String)} is used for the application user only. + */ + private boolean oraclePasswordExplicitlySet = false; + private boolean usingSid = false; public OracleContainer(String dockerImageName) { @@ -112,7 +124,8 @@ public String getUsername() { @Override public String getPassword() { - return password; + // When connecting via SID we authenticate as SYSTEM. Use the dedicated system password. + return isUsingSid() ? oraclePassword : password; } @Override @@ -142,6 +155,27 @@ public OracleContainer withPassword(String password) { throw new IllegalArgumentException("Password cannot be null or empty"); } this.password = password; + // Maintain backwards compatibility: if oracle password wasn't set explicitly, + // align it with the application user's password. + if (!oraclePasswordExplicitlySet) { + this.oraclePassword = password; + } + return self(); + } + + /** + * Sets the password for the Oracle system user (SYSTEM/SYS). This is independent from the + * application user password set via {@link #withPassword(String)}. + * + * @param oraclePassword password for SYSTEM/SYS users inside the container + * @return this container instance + */ + public OracleContainer withOraclePassword(String oraclePassword) { + if (StringUtils.isEmpty(oraclePassword)) { + throw new IllegalArgumentException("Oracle password cannot be null or empty"); + } + this.oraclePassword = oraclePassword; + this.oraclePasswordExplicitlySet = true; return self(); } @@ -185,7 +219,8 @@ public String getTestQueryString() { @Override protected void configure() { - withEnv("ORACLE_PASSWORD", password); + // Configure system user password independently from application user's password + withEnv("ORACLE_PASSWORD", oraclePassword); // Only set ORACLE_DATABASE if different than the default. if (databaseName != DEFAULT_DATABASE_NAME) { diff --git a/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java b/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java index 45ed72cb622..e50e63ca74d 100644 --- a/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java +++ b/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java @@ -31,6 +31,20 @@ private void runTest(OracleContainer container, String databaseName, String user assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); } + private void runTestSystemUser(OracleContainer container, String databaseName, String username, String password) + throws SQLException { + //Test config was honored + assertThat(container.getDatabaseName()).isEqualTo(databaseName); + assertThat(container.getUsername()).isEqualTo(username); + assertThat(container.getPassword()).isEqualTo(password); + + //Test we can get a connection and execute a system-level command + container.start(); + ResultSet resultSet = performQuery(container, "GRANT DBA TO " + username); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic system user query succeeds").isEqualTo(1); + } + @Test public void testDefaultSettings() throws SQLException { try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME)) { @@ -77,7 +91,7 @@ public void testCustomUser() throws SQLException { @Test public void testSID() throws SQLException { try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME).usingSid()) { - runTest(oracle, "freepdb1", "system", "test"); + runTestSystemUser(oracle, "freepdb1", "system", "test"); // Match against the last ':' String urlSuffix = oracle.getJdbcUrl().split("(\\:)(?!.*\\:)", 2)[1]; @@ -92,7 +106,29 @@ public void testSIDAndCustomPassword() throws SQLException { .usingSid() .withPassword("testPassword") ) { - runTest(oracle, "freepdb1", "system", "testPassword"); + runTestSystemUser(oracle, "freepdb1", "system", "testPassword"); + } + } + + @Test + public void testSeparateSystemAndAppPasswords() throws SQLException { + // SID mode should use system password + try ( + OracleContainer oracleSid = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + .usingSid() + .withOraclePassword("SysP@ss1!") + .withPassword("AppP@ss1!") + ) { + runTestSystemUser(oracleSid, "freepdb1", "system", "SysP@ss1!"); + } + + // Non-SID mode should use application user's password + try ( + OracleContainer oraclePdb = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + .withOraclePassword("SysP@ss2!") + .withPassword("AppP@ss2!") + ) { + runTest(oraclePdb, "freepdb1", "test", "AppP@ss2!"); } } diff --git a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java index 07db62a7b2e..bf1f0ea53e1 100644 --- a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java +++ b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java @@ -61,6 +61,18 @@ public class OracleContainer extends JdbcDatabaseContainer { private String password = APP_USER_PASSWORD; + /** + * Password for Oracle system user (e.g. SYSTEM/SYS). Defaults to {@link #APP_USER_PASSWORD} + * for backwards compatibility, but can be customized independently via {@link #withOraclePassword(String)}. + */ + private String oraclePassword = APP_USER_PASSWORD; + + /** + * Tracks whether {@link #withOraclePassword(String)} was called to avoid overriding + * the system password when {@link #withPassword(String)} is used for the application user only. + */ + private boolean oraclePasswordExplicitlySet = false; + private boolean usingSid = false; public OracleContainer(String dockerImageName) { @@ -125,7 +137,8 @@ public String getUsername() { @Override public String getPassword() { - return password; + // When connecting via SID we authenticate as SYSTEM. Use the dedicated system password. + return isUsingSid() ? oraclePassword : password; } @Override @@ -155,6 +168,27 @@ public OracleContainer withPassword(String password) { throw new IllegalArgumentException("Password cannot be null or empty"); } this.password = password; + // Maintain backwards compatibility: if oracle password wasn't set explicitly, + // align it with the application user's password. + if (!oraclePasswordExplicitlySet) { + this.oraclePassword = password; + } + return self(); + } + + /** + * Sets the password for the Oracle system user (SYSTEM/SYS). This is independent from the + * application user password set via {@link #withPassword(String)}. + * + * @param oraclePassword password for SYSTEM/SYS users inside the container + * @return this container instance + */ + public OracleContainer withOraclePassword(String oraclePassword) { + if (StringUtils.isEmpty(oraclePassword)) { + throw new IllegalArgumentException("Oracle password cannot be null or empty"); + } + this.oraclePassword = oraclePassword; + this.oraclePasswordExplicitlySet = true; return self(); } @@ -203,7 +237,8 @@ public String getTestQueryString() { @Override protected void configure() { - withEnv("ORACLE_PASSWORD", password); + // Configure system user password independently from application user's password + withEnv("ORACLE_PASSWORD", oraclePassword); // Only set ORACLE_DATABASE if different than the default. if (databaseName != DEFAULT_DATABASE_NAME) { diff --git a/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java b/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java index 82ef9846ab2..6c7a561b871 100644 --- a/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java +++ b/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java @@ -31,6 +31,20 @@ private void runTest(OracleContainer container, String databaseName, String user assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); } + private void runTestSystemUser(OracleContainer container, String databaseName, String username, String password) + throws SQLException { + //Test config was honored + assertThat(container.getDatabaseName()).isEqualTo(databaseName); + assertThat(container.getUsername()).isEqualTo(username); + assertThat(container.getPassword()).isEqualTo(password); + + //Test we can get a connection and execute a system-level command + container.start(); + ResultSet resultSet = performQuery(container, "GRANT DBA TO " + username); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic system user query succeeds").isEqualTo(1); + } + @Test public void testDefaultSettings() throws SQLException { try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME);) { @@ -77,7 +91,7 @@ public void testCustomUser() throws SQLException { @Test public void testSID() throws SQLException { try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME).usingSid();) { - runTest(oracle, "xepdb1", "system", "test"); + runTestSystemUser(oracle, "xepdb1", "system", "test"); // Match against the last ':' String urlSuffix = oracle.getJdbcUrl().split("(\\:)(?!.*\\:)", 2)[1]; @@ -92,7 +106,29 @@ public void testSIDAndCustomPassword() throws SQLException { .usingSid() .withPassword("testPassword"); ) { - runTest(oracle, "xepdb1", "system", "testPassword"); + runTestSystemUser(oracle, "xepdb1", "system", "testPassword"); + } + } + + @Test + public void testSeparateSystemAndAppPasswords() throws SQLException { + // SID mode should use system password + try ( + OracleContainer oracleSid = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + .usingSid() + .withOraclePassword("SysP@ss1!") + .withPassword("AppP@ss1!") + ) { + runTestSystemUser(oracleSid, "xepdb1", "system", "SysP@ss1!"); + } + + // Non-SID mode should use application user's password + try ( + OracleContainer oraclePdb = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + .withOraclePassword("SysP@ss2!") + .withPassword("AppP@ss2!") + ) { + runTest(oraclePdb, "xepdb1", "test", "AppP@ss2!"); } }