Message-ID: From: "vlsi (@vlsi)" To: "pgjdbc/pgjdbc" Date: Tue, 02 Jun 2026 05:48:04 +0000 Subject: [pgjdbc/pgjdbc] PR #4125: test: verify custom properties reach socket factory List-Id: X-GitHub-Additions: 135 X-GitHub-Author-Id: 213894 X-GitHub-Author-Login: vlsi X-GitHub-Base: master X-GitHub-Changed-Files: 2 X-GitHub-Commits: 1 X-GitHub-Deletions: 0 X-GitHub-Head-Branch: codex/socket-factory-custom-properties X-GitHub-Head-SHA: f7933d92c922ca59af524cf215d87811a4184ba2 X-GitHub-Issue: 4125 X-GitHub-Labels: building-and-testing X-GitHub-Merge-SHA: 28c1dcaf99da00457586a80960fc6399841dd920 X-GitHub-Merged-By: vlsi X-GitHub-Repo: pgjdbc/pgjdbc X-GitHub-State: merged X-GitHub-Type: pull_request X-GitHub-Url: https://github.com/pgjdbc/pgjdbc/pull/4125 Content-Type: text/plain; charset=utf-8 ## Summary - add a SocketFactory fixture that captures constructor Properties and can participate in real connections - verify custom properties reach SocketFactory from explicit Properties via SocketFactoryFactory and a real connection - verify custom URL parameters reach SocketFactory after Driver.parseURL and during a real connection ## Tests - ./gradlew --quiet :postgresql:checkstyleTest - ./gradlew --quiet :postgresql:test --tests org.postgresql.test.util.ObjectFactoryTest *(fails locally because PostgreSQL is not listening on localhost:5432; the new real-connection tests and existing invalidAuthenticationPlugin all hit the same connection-refused setup issue)* diff --git a/pgjdbc/src/test/java/org/postgresql/test/util/CapturingSocketFactory.java b/pgjdbc/src/test/java/org/postgresql/test/util/CapturingSocketFactory.java new file mode 100644 index 0000000000..4c68e320f0 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/util/CapturingSocketFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; + +import javax.net.SocketFactory; + +public class CapturingSocketFactory extends SocketFactory { + private static final AtomicReference LAST_PROPERTIES = new AtomicReference<>(); + + final Properties properties; + + public CapturingSocketFactory(Properties properties) { + this.properties = properties; + LAST_PROPERTIES.set(properties); + } + + static void reset() { + LAST_PROPERTIES.set(null); + } + + static Properties getLastProperties() { + return LAST_PROPERTIES.get(); + } + + @Override + public Socket createSocket() { + return new Socket(); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return new Socket(host, port); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException { + return new Socket(host, port, localHost, localPort); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return new Socket(host, port); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, + int localPort) throws IOException { + return new Socket(address, port, localAddress, localPort); + } +} diff --git a/pgjdbc/src/test/java/org/postgresql/test/util/ObjectFactoryTest.java b/pgjdbc/src/test/java/org/postgresql/test/util/ObjectFactoryTest.java index a04b8eac04..5c8ef29b8c 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/util/ObjectFactoryTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/util/ObjectFactoryTest.java @@ -8,9 +8,14 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; +import org.postgresql.Driver; import org.postgresql.PGProperty; +import org.postgresql.core.SocketFactoryFactory; import org.postgresql.jdbc.SslMode; import org.postgresql.test.TestUtil; import org.postgresql.util.ObjectFactory; @@ -19,6 +24,7 @@ import org.junit.jupiter.api.Test; import org.opentest4j.MultipleFailuresError; +import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; @@ -95,6 +101,75 @@ void invalidSslHostnameVerifier() { testInvalidInstantiation(PGProperty.SSL_HOSTNAME_VERIFIER, PSQLState.CONNECTION_FAILURE); } + @Test + void socketFactoryReceivesCustomProperties() throws SQLException { + Properties props = new Properties(); + PGProperty.SOCKET_FACTORY.set(props, CapturingSocketFactory.class.getName()); + props.setProperty("x-acme-customfield", "from-properties"); + + SocketFactory socketFactory = SocketFactoryFactory.getSocketFactory(props); + + CapturingSocketFactory capturingSocketFactory = + assertInstanceOf(CapturingSocketFactory.class, socketFactory); + assertSame(props, capturingSocketFactory.properties); + assertEquals("from-properties", + capturingSocketFactory.properties.getProperty("x-acme-customfield")); + } + + @Test + void socketFactoryReceivesCustomPropertiesFromUrl() throws SQLException { + Properties props = Driver.parseURL( + "jdbc:postgresql://localhost/test?socketFactory=" + + CapturingSocketFactory.class.getName() + + "&x-acme-customfield=from-url", + null); + assertNotNull(props); + + SocketFactory socketFactory = SocketFactoryFactory.getSocketFactory(props); + + CapturingSocketFactory capturingSocketFactory = + assertInstanceOf(CapturingSocketFactory.class, socketFactory); + assertSame(props, capturingSocketFactory.properties); + assertEquals("from-url", + capturingSocketFactory.properties.getProperty("x-acme-customfield")); + } + + @Test + void socketFactoryReceivesCustomPropertiesOnConnection() throws SQLException { + CapturingSocketFactory.reset(); + Properties props = new Properties(); + PGProperty.SOCKET_FACTORY.set(props, CapturingSocketFactory.class.getName()); + props.setProperty("x-acme-customfield", "from-properties-connection"); + + try (Connection connection = TestUtil.openDB(props)) { + assertNotNull(connection); + } + + Properties socketFactoryProperties = CapturingSocketFactory.getLastProperties(); + assertNotNull(socketFactoryProperties); + assertEquals("from-properties-connection", + socketFactoryProperties.getProperty("x-acme-customfield")); + } + + @Test + void socketFactoryReceivesCustomUrlPropertiesOnConnection() throws SQLException { + CapturingSocketFactory.reset(); + Properties props = new Properties(); + TestUtil.setTestUrlProperty(props, PGProperty.SOCKET_FACTORY, + CapturingSocketFactory.class.getName()); + props.setProperty(TestUtil.TEST_URL_PROPERTY_PREFIX + "x-acme-customfield", + "from-url-connection"); + + try (Connection connection = TestUtil.openDB(props)) { + assertNotNull(connection); + } + + Properties socketFactoryProperties = CapturingSocketFactory.getLastProperties(); + assertNotNull(socketFactoryProperties); + assertEquals("from-url-connection", + socketFactoryProperties.getProperty("x-acme-customfield")); + } + @Test void instantiateInvalidSocketFactory() { Properties props = new Properties();