diff --git a/automation/Makefile b/automation/Makefile index 053135562..ea35770c0 100755 --- a/automation/Makefile +++ b/automation/Makefile @@ -141,8 +141,8 @@ sync_jdbc_config: cp $(TEMPLATES_DIR)/jdbc-site.xml $(PXF_BASE_SERVERS)/database/; \ sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_DRIVER_CLASS_NAME|org.postgresql.Driver|" $(PXF_BASE_SERVERS)/database/jdbc-site.xml; \ sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_URL|jdbc:postgresql://$(JDBC_HOST):$(JDBC_PORT)/pxfautomation|" $(PXF_BASE_SERVERS)/database/jdbc-site.xml; \ - sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_USER||" $(PXF_BASE_SERVERS)/database/jdbc-site.xml; \ - sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_PASSWORD||" $(PXF_BASE_SERVERS)/database/jdbc-site.xml; \ + sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_USER|gpadmin|" $(PXF_BASE_SERVERS)/database/jdbc-site.xml; \ + sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_PASSWORD|gpadmin|" $(PXF_BASE_SERVERS)/database/jdbc-site.xml; \ cp $(PXF_BASE_SERVERS)/database/jdbc-site.xml $(PXF_BASE_SERVERS)/database/testuser-user.xml; \ sed $(SED_OPTS) "s|pxfautomation|template1|" $(PXF_BASE_SERVERS)/database/testuser-user.xml; \ fi @@ -152,8 +152,8 @@ sync_jdbc_config: cp $(TEMPLATES_DIR)/jdbc-site.xml $(PXF_BASE_SERVERS)/db-session-params/; \ sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_DRIVER_CLASS_NAME|org.postgresql.Driver|" $(PXF_BASE_SERVERS)/db-session-params/jdbc-site.xml; \ sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_URL|jdbc:postgresql://$(JDBC_HOST):$(JDBC_PORT)/pxfautomation|" $(PXF_BASE_SERVERS)/db-session-params/jdbc-site.xml; \ - sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_USER||" $(PXF_BASE_SERVERS)/db-session-params/jdbc-site.xml; \ - sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_PASSWORD||" $(PXF_BASE_SERVERS)/db-session-params/jdbc-site.xml; \ + sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_USER|gpadmin|" $(PXF_BASE_SERVERS)/db-session-params/jdbc-site.xml; \ + sed $(SED_OPTS) "s|YOUR_DATABASE_JDBC_PASSWORD|gpadmin|" $(PXF_BASE_SERVERS)/db-session-params/jdbc-site.xml; \ sed $(SED_OPTS) "s||jdbc.session.property.client_min_messagesdebug1|" $(PXF_BASE_SERVERS)/db-session-params/jdbc-site.xml; \ sed $(SED_OPTS) "s||jdbc.session.property.default_statistics_target123|" $(PXF_BASE_SERVERS)/db-session-params/jdbc-site.xml; \ fi diff --git a/automation/src/main/java/org/apache/cloudberry/pxf/automation/applications/PXFApplication.java b/automation/src/main/java/org/apache/cloudberry/pxf/automation/applications/PXFApplication.java index ea67fb28f..dc9070a4b 100644 --- a/automation/src/main/java/org/apache/cloudberry/pxf/automation/applications/PXFApplication.java +++ b/automation/src/main/java/org/apache/cloudberry/pxf/automation/applications/PXFApplication.java @@ -52,8 +52,8 @@ public void configureJdbcServers() throws IOException, InterruptedException { "cp ${TEMPLATES_DIR}/jdbc-site.xml ${PXF_BASE_SERVERS}/database/", "sed -i 's|YOUR_DATABASE_JDBC_DRIVER_CLASS_NAME|org.postgresql.Driver|' ${PXF_BASE_SERVERS}/database/jdbc-site.xml", "sed -i 's|YOUR_DATABASE_JDBC_URL|jdbc:postgresql://localhost:7000/pxfautomation|' ${PXF_BASE_SERVERS}/database/jdbc-site.xml", - "sed -i 's|YOUR_DATABASE_JDBC_USER||' ${PXF_BASE_SERVERS}/database/jdbc-site.xml", - "sed -i 's|YOUR_DATABASE_JDBC_PASSWORD||' ${PXF_BASE_SERVERS}/database/jdbc-site.xml", + "sed -i 's|YOUR_DATABASE_JDBC_USER|gpadmin|' ${PXF_BASE_SERVERS}/database/jdbc-site.xml", + "sed -i 's|YOUR_DATABASE_JDBC_PASSWORD|gpadmin|' ${PXF_BASE_SERVERS}/database/jdbc-site.xml", "cp ${PXF_BASE_SERVERS}/database/jdbc-site.xml ${PXF_BASE_SERVERS}/database/testuser-user.xml", "sed -i 's|pxfautomation|template1|' ${PXF_BASE_SERVERS}/database/testuser-user.xml", "cp /home/gpadmin/workspace/cloudberry-pxf/automation/src/test/resources/report.sql ${PXF_BASE_SERVERS}/database/", @@ -62,8 +62,8 @@ public void configureJdbcServers() throws IOException, InterruptedException { "cp ${TEMPLATES_DIR}/jdbc-site.xml ${PXF_BASE_SERVERS}/db-session-params/", "sed -i 's|YOUR_DATABASE_JDBC_DRIVER_CLASS_NAME|org.postgresql.Driver|' ${PXF_BASE_SERVERS}/db-session-params/jdbc-site.xml", "sed -i 's|YOUR_DATABASE_JDBC_URL|jdbc:postgresql://localhost:7000/pxfautomation|' ${PXF_BASE_SERVERS}/db-session-params/jdbc-site.xml", - "sed -i 's|YOUR_DATABASE_JDBC_USER||' ${PXF_BASE_SERVERS}/db-session-params/jdbc-site.xml", - "sed -i 's|YOUR_DATABASE_JDBC_PASSWORD||' ${PXF_BASE_SERVERS}/db-session-params/jdbc-site.xml", + "sed -i 's|YOUR_DATABASE_JDBC_USER|gpadmin|' ${PXF_BASE_SERVERS}/db-session-params/jdbc-site.xml", + "sed -i 's|YOUR_DATABASE_JDBC_PASSWORD|gpadmin|' ${PXF_BASE_SERVERS}/db-session-params/jdbc-site.xml", "sed -i 's||jdbc.session.property.client_min_messagesdebug1|' ${PXF_BASE_SERVERS}/db-session-params/jdbc-site.xml", "sed -i 's||jdbc.session.property.default_statistics_target123|' ${PXF_BASE_SERVERS}/db-session-params/jdbc-site.xml", diff --git a/server/pxf-jdbc/src/main/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePlugin.java b/server/pxf-jdbc/src/main/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePlugin.java index 6a500a1ac..0ce2a8777 100644 --- a/server/pxf-jdbc/src/main/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePlugin.java +++ b/server/pxf-jdbc/src/main/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePlugin.java @@ -30,6 +30,8 @@ import org.apache.cloudberry.pxf.plugins.jdbc.utils.ConnectionManager; import org.apache.cloudberry.pxf.plugins.jdbc.utils.DbProduct; import org.apache.cloudberry.pxf.plugins.jdbc.utils.HiveJdbcUtils; +import org.postgresql.Driver; +import org.postgresql.PGProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -97,6 +99,7 @@ public class JdbcBasePlugin extends BasePlugin { private static final String HIVE_DEFAULT_DRIVER_CLASS = "org.apache.hive.jdbc.HiveDriver"; private static final String MYSQL_DRIVER_PREFIX = "com.mysql."; private static final String JDBC_DATE_WIDE_RANGE = "jdbc.date.wideRange"; + private static final String POSTGRESQL_URL_PREFIX = "jdbc:postgresql://"; private enum TransactionIsolation { READ_UNCOMMITTED(1), @@ -315,11 +318,9 @@ public void afterPropertiesSet() { jdbcUser = context.getUser(); } } - if (jdbcUser != null) { + if (jdbcUser != null && !jdbcUser.isEmpty()) { LOG.debug("Effective JDBC user {}", jdbcUser); connectionConfiguration.setProperty("user", jdbcUser); - } else { - LOG.debug("JDBC user has not been set"); } if (LOG.isDebugEnabled()) { @@ -332,14 +333,16 @@ public void afterPropertiesSet() { // This must be the last parameter parsed, as we output connectionConfiguration earlier // Optional parameter. By default, corresponding connectionConfiguration property is not set + String jdbcPassword = configuration.get(JDBC_PASSWORD_PROPERTY_NAME); if (jdbcUser != null) { - String jdbcPassword = configuration.get(JDBC_PASSWORD_PROPERTY_NAME); - if (jdbcPassword != null) { + if (jdbcPassword != null && !jdbcPassword.isEmpty()) { LOG.debug("Connection password: {}", ConnectionManager.maskPassword(jdbcPassword)); connectionConfiguration.setProperty("password", jdbcPassword); } } + assertJDBC(jdbcUrl, jdbcUser, jdbcPassword); + // connection pool is optional, enabled by default isConnectionPoolUsed = configuration.getBoolean(JDBC_CONNECTION_POOL_ENABLED_PROPERTY_NAME, true); LOG.debug("Connection pool is {}enabled", isConnectionPoolUsed ? "" : "not "); @@ -401,6 +404,38 @@ public Connection getConnection() throws SQLException { return connection; } + /** + * Asserts whether all required JDBC URL params/properties set, throws IllegalArgumentException otherwise + * + * @param jdbcUrl connection string + * @param username username from properties (outside of connection string) + * @param password password from properties (outside of connection string) + */ + private void assertJDBC(String jdbcUrl, String username, String password) { + if (!StringUtils.startsWith(jdbcUrl, POSTGRESQL_URL_PREFIX)) { + return; + } + + // For PostgreSQL we enforce users to specify username and password + Properties urlProperties = Driver.parseURL(jdbcUrl, null); + if (urlProperties == null) { + throw new IllegalArgumentException("Invalid PostgreSQL JDBC URL format provided: " + jdbcUrl); + } + + String urlUsername = urlProperties.getProperty(PGProperty.USER.getName()); + if (StringUtils.isEmpty(username) && StringUtils.isEmpty(urlUsername)) { + LOG.debug("PostgreSQL JDBC user has not been set"); + throw new IllegalArgumentException("PostgreSQL JDBC user has not been set"); + } + + String urlPassword = urlProperties.getProperty(PGProperty.PASSWORD.getName()); + if (StringUtils.isEmpty(password) && StringUtils.isEmpty(urlPassword)) { + throw new IllegalArgumentException("PostgreSQL JDBC password has not been set"); + } + + // Ok. It is fine to work with this postgres datasource +} + /** * Prepare a JDBC PreparedStatement * diff --git a/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcAccessorTest.java b/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcAccessorTest.java index e659fbc67..0f1ed990b 100644 --- a/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcAccessorTest.java +++ b/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcAccessorTest.java @@ -58,6 +58,8 @@ public void setup() { accessor = new JdbcAccessor(mockConnectionManager, mockSecureLogin); configuration = new Configuration(); + configuration.set("jdbc.user", "test-user"); + configuration.set("jdbc.password", "test-password"); context = new RequestContext(); context.setConfig("default"); context.setDataSource("test-table"); diff --git a/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePluginTest.java b/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePluginTest.java index c2a1baed2..8ff6010c2 100644 --- a/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePluginTest.java +++ b/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePluginTest.java @@ -23,6 +23,7 @@ import org.apache.cloudberry.pxf.api.model.RequestContext; import org.apache.cloudberry.pxf.api.security.SecureLogin; import org.apache.cloudberry.pxf.plugins.jdbc.utils.ConnectionManager; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -71,9 +72,19 @@ public class JdbcBasePluginTest { private RequestContext context; private Properties poolProps; + private Properties getDefaultConnectionProperties() { + Properties properties = new Properties(); + properties.setProperty("user", "test-user"); + properties.setProperty("password", "test-password"); + return properties; + } + @BeforeEach public void before() { configuration = new Configuration(); + configuration.set("jdbc.user", "test-user"); + configuration.set("jdbc.password", "test-password"); + context = new RequestContext(); context.setConfig("default"); context.setDataSource("test-table"); @@ -331,6 +342,134 @@ public void testGetPreparedStatementDoesNotSetQueryTimeoutIfNotSpecified() throw verify(mockStatement, never()).setQueryTimeout(anyInt()); } + @Test + public void testGetConnectionErrorWithoutPassword1() throws SQLException { + configuration.set("jdbc.driver", "org.postgresql.Driver"); + configuration.set("jdbc.url", "jdbc:postgresql://example.com/test-url"); + configuration.set("jdbc.password", ""); + + context.setServerName("test-server"); + + try { + getPlugin(mockConnectionManager, mockSecureLogin, context); + } catch (IllegalArgumentException e) { + assertEquals("PostgreSQL JDBC password has not been set", e.getMessage()); + return; + } + Assertions.fail("Expected an exception to be thrown due to missing password, but no exception was thrown."); + } + + @Test + public void testGetConnectionErrorWithoutPassword2() throws SQLException { + configuration = new Configuration(); + configuration.set("jdbc.driver", "org.postgresql.Driver"); + configuration.set("jdbc.url", "jdbc:postgresql://example.com/test-url"); + configuration.set("jdbc.user", "test-user"); + context.setConfiguration(configuration); + + context.setServerName("test-server"); + + try { + getPlugin(mockConnectionManager, mockSecureLogin, context); + } catch (IllegalArgumentException e) { + assertEquals("PostgreSQL JDBC password has not been set", e.getMessage()); + return; + } + Assertions.fail("Expected an exception to be thrown due to missing password, but no exception was thrown."); + } + + @Test + public void testGetConnectionErrorWithoutPassword3() throws SQLException { + configuration.set("jdbc.driver", "org.apache.cloudberry.pxf.plugins.jdbc.FakeJdbcDriver"); + configuration.set("jdbc.url", "jdbc:sqlserver://localhost:1433;databaseName=db1;user=admin"); + + context.setServerName("test-server"); + + // non-PostgreSQL drivers are not subject to credential validation + getPlugin(mockConnectionManager, mockSecureLogin, context); + } + + @Test + public void testGetConnectionWithPasswordInUrl() throws SQLException { + configuration = new Configuration(); + configuration.set("jdbc.driver", "org.postgresql.Driver"); + configuration.set("jdbc.url", "jdbc:postgresql://example.com/test-url?password=test-password"); + configuration.set("jdbc.user", "test-user"); + + context.setServerName("test-server"); + context.setConfiguration(configuration); + + when(mockConnectionManager.getConnection(any(), any(), any(), anyBoolean(), any(), any())).thenReturn(mockConnection); + when(mockConnection.getMetaData()).thenReturn(mockMetaData); + + JdbcBasePlugin plugin = getPlugin(mockConnectionManager, mockSecureLogin, context); + Connection conn = plugin.getConnection(); + + assertSame(mockConnection, conn); + + Properties properties = new Properties(); + properties.setProperty("user", "test-user"); + + verify(mockConnectionManager).getConnection("test-server", "jdbc:postgresql://example.com/test-url?password=test-password", properties, true, poolProps, null); + } + + @Test + public void testGetConnectionErrorWithoutUser1() throws SQLException { + configuration.set("jdbc.driver", "org.postgresql.Driver"); + configuration.set("jdbc.url", "jdbc:postgresql://example.com/test-url"); + configuration.set("jdbc.user", ""); + configuration.set("jdbc.password", "test-password"); + + context.setServerName("test-server"); + + try { + getPlugin(mockConnectionManager, mockSecureLogin, context); + } catch (IllegalArgumentException e) { + assertEquals("PostgreSQL JDBC user has not been set", e.getMessage()); + return; + } + Assertions.fail("Expected an exception to be thrown due to missing user, but no exception was thrown."); + } + + @Test + public void testGetConnectionErrorWithoutUser2() throws SQLException { + configuration = new Configuration(); + configuration.set("jdbc.password", "test-password"); + configuration.set("jdbc.driver", "org.postgresql.Driver"); + configuration.set("jdbc.url", "jdbc:postgresql://example.com/test-url"); + context.setConfiguration(configuration); + + context.setServerName("test-server"); + + try { + getPlugin(mockConnectionManager, mockSecureLogin, context); + } catch (IllegalArgumentException e) { + assertEquals("PostgreSQL JDBC user has not been set", e.getMessage()); + return; + } + Assertions.fail("Expected an exception to be thrown due to missing user, but no exception was thrown."); + } + + @Test + public void testGetConnectionWithUserAndPasswordInUrl() throws SQLException { + configuration = new Configuration(); + configuration.set("jdbc.driver", "org.postgresql.Driver"); + configuration.set("jdbc.url", "jdbc:postgresql://example.com/db?user=test-user&password=test-password"); + context.setConfiguration(configuration); + + context.setServerName("test-server"); + + when(mockConnectionManager.getConnection(any(), any(), any(), anyBoolean(), any(), any())).thenReturn(mockConnection); + when(mockConnection.getMetaData()).thenReturn(mockMetaData); + + JdbcBasePlugin plugin = getPlugin(mockConnectionManager, mockSecureLogin, context); + Connection conn = plugin.getConnection(); + + assertSame(mockConnection, conn); + + verify(mockConnectionManager).getConnection("test-server", "jdbc:postgresql://example.com/db?user=test-user&password=test-password", new Properties(), true, poolProps, null); + } + @Test public void testGetConnectionNoConnPropsPoolDisabled() throws SQLException { context.setServerName("test-server"); @@ -346,14 +485,14 @@ public void testGetConnectionNoConnPropsPoolDisabled() throws SQLException { assertSame(mockConnection, conn); - verify(mockConnectionManager).getConnection("test-server", "test-url", new Properties(), false, null, null); + verify(mockConnectionManager).getConnection("test-server", "test-url", getDefaultConnectionProperties(), false, null, null); } @Test public void testGetConnectionConnPropsPoolDisabled() throws SQLException { context.setServerName("test-server"); - Properties connProps = new Properties(); + Properties connProps = getDefaultConnectionProperties(); connProps.setProperty("foo", "foo-val"); connProps.setProperty("bar", "bar-val"); @@ -392,7 +531,7 @@ public void testGetConnectionConnPropsPoolEnabledNoPoolProps() throws SQLExcepti assertSame(mockConnection, conn); - Properties connProps = new Properties(); + Properties connProps = getDefaultConnectionProperties(); connProps.setProperty("foo", "foo-val"); connProps.setProperty("bar", "bar-val"); @@ -419,7 +558,7 @@ public void testGetConnectionConnPropsPoolEnabledWithQualifier() throws SQLExcep assertSame(mockConnection, conn); - Properties connProps = new Properties(); + Properties connProps = getDefaultConnectionProperties(); connProps.setProperty("foo", "foo-val"); connProps.setProperty("bar", "bar-val"); @@ -447,7 +586,7 @@ public void testGetConnectionConnPropsPoolEnabledPoolProps() throws SQLException assertSame(mockConnection, conn); - Properties connProps = new Properties(); + Properties connProps = getDefaultConnectionProperties(); connProps.setProperty("foo", "foo-val"); connProps.setProperty("bar", "bar-val"); @@ -478,7 +617,7 @@ public void testGetConnectionConnPropsPoolDisabledPoolProps() throws SQLExceptio assertSame(mockConnection, conn); - Properties connProps = new Properties(); + Properties connProps = getDefaultConnectionProperties(); connProps.setProperty("foo", "foo-val"); connProps.setProperty("bar", "bar-val"); diff --git a/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePluginTestInitialize.java b/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePluginTestInitialize.java index 80f1a76f7..8d71b8d5d 100644 --- a/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePluginTestInitialize.java +++ b/server/pxf-jdbc/src/test/java/org/apache/cloudberry/pxf/plugins/jdbc/JdbcBasePluginTestInitialize.java @@ -123,6 +123,8 @@ private Configuration makeConfiguration() { Configuration configuration = new Configuration(); configuration.set("jdbc.driver", JDBC_DRIVER); configuration.set("jdbc.url", JDBC_URL); + configuration.set("jdbc.user", "test-user"); + configuration.set("jdbc.password", "test-password"); return configuration; } @@ -430,6 +432,8 @@ public void testConnectionConfiguration() throws Exception { Properties expected = new Properties(); expected.setProperty(CONFIG_PROPERTIES_KEYS[0], "v1"); expected.setProperty(CONFIG_PROPERTIES_KEYS[1], "v2"); + expected.setProperty("user", "test-user"); + expected.setProperty("password", "test-password"); assertEntrySetEquals(expected.entrySet(), ((Properties) getInternalState(plugin, "connectionConfiguration")).entrySet()); } @@ -449,6 +453,7 @@ public void testUser() throws Exception { // Checks Properties expected = new Properties(); expected.setProperty("user", "user"); + expected.setProperty("password", "test-password"); assertEntrySetEquals(expected.entrySet(), ((Properties) getInternalState(plugin, "connectionConfiguration")).entrySet()); } @@ -469,6 +474,7 @@ public void testUserWithImpersonation() throws Exception { // Checks Properties expected = new Properties(); expected.setProperty("user", "proxy"); + expected.setProperty("password", "test-password"); assertEntrySetEquals(expected.entrySet(), ((Properties) getInternalState(plugin, "connectionConfiguration")).entrySet()); } @@ -490,6 +496,7 @@ public void testUserWithImpersonationOverwrite() throws Exception { // Checks Properties expected = new Properties(); expected.setProperty("user", "proxy"); + expected.setProperty("password", "test-password"); assertEntrySetEquals(expected.entrySet(), ((Properties) getInternalState(plugin, "connectionConfiguration")).entrySet()); } @@ -511,6 +518,7 @@ public void testUserWithoutImpersonationNoOverwrite() throws Exception { // Checks Properties expected = new Properties(); expected.setProperty("user", "user"); + expected.setProperty("password", "test-password"); assertEntrySetEquals(expected.entrySet(), ((Properties) getInternalState(plugin, "connectionConfiguration")).entrySet()); } @@ -531,6 +539,7 @@ public void testUserDefaultImpersonationNoOverwrite() throws Exception { // Checks Properties expected = new Properties(); expected.setProperty("user", "user"); + expected.setProperty("password", "test-password"); assertEntrySetEquals(expected.entrySet(), ((Properties) getInternalState(plugin, "connectionConfiguration")).entrySet()); } @@ -571,6 +580,8 @@ public void testPassword() throws Exception { // Checks Properties expected = new Properties(); + expected.setProperty("user", "test-user"); + expected.setProperty("password", "password"); assertEntrySetEquals(expected.entrySet(), ((Properties) getInternalState(plugin, "connectionConfiguration")).entrySet()); }