From fb0d4b9bf90a3c7b619c3f566ac9adfaaba131ab Mon Sep 17 00:00:00 2001 From: "yndu13(agent)" Date: Fri, 17 Jul 2026 18:48:23 +0800 Subject: [PATCH 1/2] Raise unit test coverage above 90% for JaCoCo gaps. Add targeted tests for logging, OkHttp CA/proxy paths, Tea helpers, and utils without relying on the CI CA secret. --- .../java/com/aliyun/tea/TeaModelTest.java | 12 +++ .../java/com/aliyun/tea/TeaResponseTest.java | 17 +++++ src/test/java/com/aliyun/tea/TeaTest.java | 40 ++++++++++ .../com/aliyun/tea/logging/LoggerTest.java | 37 +++++++++ .../aliyun/tea/okhttp/ClientHelperTest.java | 5 ++ .../tea/okhttp/OkHttpClientBuilderTest.java | 76 +++++++++++++++++++ .../aliyun/tea/okhttp/OkRequestBodyTest.java | 9 +++ .../com/aliyun/tea/utils/IOUtilsTest.java | 8 ++ .../tea/utils/TrueHostnameVerifierTest.java | 3 + .../com/aliyun/tea/utils/ValidateTest.java | 2 + src/test/resources/test-ca-dual.pem | 38 ++++++++++ src/test/resources/test-ca.pem | 19 +++++ 12 files changed, 266 insertions(+) create mode 100644 src/test/resources/test-ca-dual.pem create mode 100644 src/test/resources/test-ca.pem diff --git a/src/test/java/com/aliyun/tea/TeaModelTest.java b/src/test/java/com/aliyun/tea/TeaModelTest.java index 85d7138..3480570 100644 --- a/src/test/java/com/aliyun/tea/TeaModelTest.java +++ b/src/test/java/com/aliyun/tea/TeaModelTest.java @@ -801,5 +801,17 @@ public void confirmTypeTest() { object6 = TeaModel.confirmType(String.class, list); Assert.assertEquals("[\"1\",2,true,{\"sub\":\"sub\"}]", object6); + Assert.assertNull(TeaModel.parseObject(null)); + try { + TeaModel.confirmType(Float.class, "not-a-float"); + Assert.fail(); + } catch (NumberFormatException ignored) { + } + try { + TeaModel.confirmType(Double.class, "not-a-double"); + Assert.fail(); + } catch (NumberFormatException ignored) { + } + Assert.assertEquals(1L, TeaModel.confirmType(Long.class, 1.5F)); } } diff --git a/src/test/java/com/aliyun/tea/TeaResponseTest.java b/src/test/java/com/aliyun/tea/TeaResponseTest.java index 1b02954..5d4d88d 100644 --- a/src/test/java/com/aliyun/tea/TeaResponseTest.java +++ b/src/test/java/com/aliyun/tea/TeaResponseTest.java @@ -25,4 +25,21 @@ public void getResponseBodyTest() throws Exception{ response.body = new ByteArrayInputStream("test".getBytes("UTF-8")); Assert.assertEquals("test", response.getResponseBody()); } + + @Test + public void getResponseBodyExceptionTest() { + TeaResponse response = new TeaResponse(); + response.body = new InputStream() { + @Override + public int read() throws java.io.IOException { + throw new java.io.IOException("read failed"); + } + }; + try { + response.getResponseBody(); + Assert.fail(); + } catch (TeaException e) { + Assert.assertEquals("read failed", e.getMessage()); + } + } } diff --git a/src/test/java/com/aliyun/tea/TeaTest.java b/src/test/java/com/aliyun/tea/TeaTest.java index 2269836..26112e1 100644 --- a/src/test/java/com/aliyun/tea/TeaTest.java +++ b/src/test/java/com/aliyun/tea/TeaTest.java @@ -287,4 +287,44 @@ public void toReadableTest() throws IOException { String result = new String(bytes, 0, index); Assert.assertTrue(str.equals(result)); } + + @Test + public void doActionWithNullChainTest() { + ClientHelper.clients.clear(); + TeaRequest request = new TeaRequest(); + Map map = new HashMap<>(); + map.put("host", "www.google.com.hk"); + request.protocol = "http"; + request.headers = map; + request.method = "GET"; + Map runtimeOptions = new HashMap<>(); + runtimeOptions.put("readTimeout", "50000"); + runtimeOptions.put("connectTimeout", "50000"); + TeaResponse response = Tea.doAction(request, runtimeOptions, null); + Assert.assertEquals(200, response.statusCode); + } + + @Test + public void toWriteableTest() { + Assert.assertNotNull(Tea.toWriteable(16)); + try { + Tea.toWriteable(-1); + Assert.fail(); + } catch (TeaException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void sleepInterruptedTest() { + Thread.currentThread().interrupt(); + try { + Tea.sleep(1000); + Assert.fail(); + } catch (TeaException e) { + Assert.assertNotNull(e.getMessage()); + } finally { + Thread.interrupted(); + } + } } diff --git a/src/test/java/com/aliyun/tea/logging/LoggerTest.java b/src/test/java/com/aliyun/tea/logging/LoggerTest.java index bcfb55d..7804ac6 100644 --- a/src/test/java/com/aliyun/tea/logging/LoggerTest.java +++ b/src/test/java/com/aliyun/tea/logging/LoggerTest.java @@ -141,4 +141,41 @@ public void errorTest() { logger.error("test: {}, {}", "key1", "key2"); logger.error("test: {}, {}, {}", "key1", "key2", "key3"); } + + @Test + public void defaultLoggerOverloadsTest() { + System.setProperty(DefaultLogger.SDK_LOG_LEVEL, "verbose"); + DefaultLogger logger = new DefaultLogger("com.aliyun.tea.logging.NotExistClass"); + Assert.assertEquals("com.aliyun.tea.logging.NotExistClass", logger.getName()); + + logger.trace("trace {}", "a"); + logger.trace("trace", new RuntimeException("t")); + logger.debug("debug {}", "a"); + logger.debug("debug {} {}", "a", "b"); + logger.debug("debug", new RuntimeException("d")); + logger.info("info {}", "a"); + logger.info("info {} {}", "a", "b"); + logger.info("info", new RuntimeException("i")); + logger.warn("warn {}", "a"); + logger.warn("warn {} {}", "a", "b"); + logger.warn("warn", new RuntimeException("w")); + logger.error("error {}", "a"); + logger.error("error {} {}", "a", "b"); + logger.error("error", new RuntimeException("e")); + } + + @Test + public void logLevelFromStringNullTest() { + Assert.assertEquals(LogLevel.NOT_SET, LogLevel.fromString(null)); + } + + @Test + public void clientLoggerThrowableInArgsTest() { + System.setProperty(DefaultLogger.SDK_LOG_LEVEL, "warn"); + ClientLogger logger = new ClientLogger(LoggerTest.class); + logger.warning("warn with throwable: {}", "x", new RuntimeException("warn-ex")); + logger.error("error with throwable: {}", "y", new RuntimeException("error-ex")); + logger.verbose("empty-args"); + logger.verbose("format-only", new Object[0]); + } } diff --git a/src/test/java/com/aliyun/tea/okhttp/ClientHelperTest.java b/src/test/java/com/aliyun/tea/okhttp/ClientHelperTest.java index 85e981b..cc29cc4 100644 --- a/src/test/java/com/aliyun/tea/okhttp/ClientHelperTest.java +++ b/src/test/java/com/aliyun/tea/okhttp/ClientHelperTest.java @@ -55,6 +55,11 @@ public void getClientKeyTest() throws NoSuchMethodException, InvocationTargetExc map.put("callTimeout", null); str = (String) getClientKey.invoke(ClientHelper.class, "0:0:0:0:0:0:0:1", 0, map); Assert.assertEquals("0:0:0:0:0:0:0:1:0:http://127.0.0.1:80:https://127.0.0.1:80:socks5://user:password@127.0.0.1:1080:1000:2000:false", str); + + map.put("keepAliveDuration", 10000); + map.put("maxIdleConns", 5); + str = (String) getClientKey.invoke(ClientHelper.class, "0:0:0:0:0:0:0:1", 0, map); + Assert.assertEquals("0:0:0:0:0:0:0:1:0:http://127.0.0.1:80:https://127.0.0.1:80:socks5://user:password@127.0.0.1:1080:1000:2000:10000:5:false", str); } @Test diff --git a/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java b/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java index f25afb7..baea179 100644 --- a/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java +++ b/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java @@ -4,17 +4,32 @@ import com.aliyun.tea.utils.DefaultHostnameVerifier; import okhttp3.OkHttpClient; import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.Route; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.Scanner; public class OkHttpClientBuilderTest { private Map map = new HashMap<>(); + private static String readResource(String name) { + InputStream is = OkHttpClientBuilderTest.class.getClassLoader().getResourceAsStream(name); + Assert.assertNotNull(is); + try (Scanner scanner = new Scanner(is, StandardCharsets.UTF_8.name())) { + scanner.useDelimiter("\\A"); + return scanner.hasNext() ? scanner.next() : ""; + } + } + @Test public void timeOutTest() { map.clear(); @@ -176,6 +191,67 @@ public void certificateTest() throws IOException { } catch (Exception e) { Assert.assertTrue(e instanceof TeaException); } + + // valid CA PEM covers split + trust store path without requiring CI CA secret + map.clear(); + map.put("ignoreSSL", false); + map.put("ca", readResource("test-ca.pem")); + OkHttpClient clientWithCa = new OkHttpClientBuilder().certificate(map).buildOkHttpClient(); + Assert.assertNotNull(clientWithCa.sslSocketFactory()); + + map.put("ca", readResource("test-ca-dual.pem")); + Assert.assertNotNull(new OkHttpClientBuilder().certificate(map).buildOkHttpClient().sslSocketFactory()); + + // non-PEM CA still enters splitPemCertificates else-branch + map.put("ca", "not-a-pem-certificate"); + try { + new OkHttpClientBuilder().certificate(map); + Assert.fail(); + } catch (TeaException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void proxyAuthenticatorTest() throws Exception { + map.clear(); + OkHttpClientBuilder clientBuilder = new OkHttpClientBuilder(); + map.put("httpsProxy", "https://user:password@127.0.0.1:8080"); + OkHttpClient client = clientBuilder.proxy(map).proxyAuthenticator(map).buildOkHttpClient(); + Assert.assertNotNull(client.proxyAuthenticator()); + + Request request = new Request.Builder().url("http://example.com").build(); + Response response = new Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(407) + .message("Proxy Authentication Required") + .build(); + Request authenticated = client.proxyAuthenticator().authenticate((Route) null, response); + Assert.assertNotNull(authenticated); + Assert.assertNotNull(authenticated.header("Proxy-Authorization")); + + map.clear(); + map.put("socks5Proxy", "socks5://user:password@127.0.0.1:1080"); + Assert.assertNotNull(new OkHttpClientBuilder().proxy(map).proxyAuthenticator(map).buildOkHttpClient()); + + map.clear(); + map.put("httpProxy", "://bad-url"); + try { + new OkHttpClientBuilder().proxy(map); + Assert.fail(); + } catch (TeaException e) { + Assert.assertNotNull(e.getMessage()); + } + + map.clear(); + map.put("httpProxy", "://bad-url"); + try { + new OkHttpClientBuilder().proxyAuthenticator(map); + Assert.fail(); + } catch (TeaException e) { + Assert.assertNotNull(e.getMessage()); + } } @Test diff --git a/src/test/java/com/aliyun/tea/okhttp/OkRequestBodyTest.java b/src/test/java/com/aliyun/tea/okhttp/OkRequestBodyTest.java index ff15e09..7d350e3 100644 --- a/src/test/java/com/aliyun/tea/okhttp/OkRequestBodyTest.java +++ b/src/test/java/com/aliyun/tea/okhttp/OkRequestBodyTest.java @@ -44,5 +44,14 @@ public void writeToTest() throws IOException { Mockito.verify(body).writeTo(sink); } + @Test + public void contentLengthTest() throws IOException { + TeaRequest request = new TeaRequest(); + OkRequestBody body = new OkRequestBody(request); + Assert.assertEquals(-1L, body.contentLength()); + request.body = new ByteArrayInputStream("tes".getBytes("UTF-8")); + body = new OkRequestBody(request); + Assert.assertEquals(3L, body.contentLength()); + } } diff --git a/src/test/java/com/aliyun/tea/utils/IOUtilsTest.java b/src/test/java/com/aliyun/tea/utils/IOUtilsTest.java index d51ca94..ba24cb3 100644 --- a/src/test/java/com/aliyun/tea/utils/IOUtilsTest.java +++ b/src/test/java/com/aliyun/tea/utils/IOUtilsTest.java @@ -13,6 +13,13 @@ public void closeQuietlyTest() { InputStream inputStream = new ByteArrayInputStream(source); try { IOUtils.closeQuietly(inputStream); + IOUtils.closeQuietly(null); + IOUtils.closeQuietly(new AutoCloseable() { + @Override + public void close() throws Exception { + throw new IOException("close failed"); + } + }); } catch (Exception e) { Assert.fail(); } @@ -22,6 +29,7 @@ public void closeQuietlyTest() { public void closeIfCloseableTest() { try { IOUtils.closeIfCloseable("test"); + IOUtils.closeIfCloseable(new ByteArrayInputStream(new byte[]{1})); } catch (Exception e) { Assert.fail(); } diff --git a/src/test/java/com/aliyun/tea/utils/TrueHostnameVerifierTest.java b/src/test/java/com/aliyun/tea/utils/TrueHostnameVerifierTest.java index 0718daa..1b5c094 100644 --- a/src/test/java/com/aliyun/tea/utils/TrueHostnameVerifierTest.java +++ b/src/test/java/com/aliyun/tea/utils/TrueHostnameVerifierTest.java @@ -16,6 +16,9 @@ public void trueHostnameVerifierTest() throws InvocationTargetException, Instant Assert.assertTrue(trueHostnameVerifier.verify("authType", sslSession)); Assert.assertTrue(trueHostnameVerifier.verify(null, null)); + HostnameVerifier defaultVerifier = DefaultHostnameVerifier.getInstance(false); + Assert.assertFalse(defaultVerifier instanceof DefaultHostnameVerifier); + Constructor constructor = DefaultHostnameVerifier.class.getDeclaredConstructor(boolean.class); constructor.setAccessible(true); DefaultHostnameVerifier hostnameVerifier = constructor.newInstance(false); diff --git a/src/test/java/com/aliyun/tea/utils/ValidateTest.java b/src/test/java/com/aliyun/tea/utils/ValidateTest.java index 80dfa05..ef532c5 100644 --- a/src/test/java/com/aliyun/tea/utils/ValidateTest.java +++ b/src/test/java/com/aliyun/tea/utils/ValidateTest.java @@ -6,6 +6,7 @@ public class ValidateTest { @Test public void isTrueTest() { + Validate.isTrue(true, "message: %s", "test"); try { Validate.isTrue(false, "message: %s", "test"); } catch (Exception e) { @@ -27,6 +28,7 @@ public void notNullTest() { @Test public void isNullTest() { + Validate.isNull(null, "message: %s", "test"); try { Validate.isNull("not null", "message: %s", "test"); } catch (Exception e) { diff --git a/src/test/resources/test-ca-dual.pem b/src/test/resources/test-ca-dual.pem new file mode 100644 index 0000000..03488a7 --- /dev/null +++ b/src/test/resources/test-ca-dual.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIDCzCCAfOgAwIBAgIUYm8J65XSPCtliio0zuVRkze8ke8wDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKdGVzdC5sb2NhbDAeFw0yNjA3MTcxMDQ1MjBaFw0yNzA3 +MTcxMDQ1MjBaMBUxEzARBgNVBAMMCnRlc3QubG9jYWwwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDEY4OqEKrlPu48ZgR60aKBcU6hONfoUO2HBm5FP76V +zkiSFkZlPL5FkZMUQ5PWTFuM74WasxGo5CWDpwexDczCKwBWnYCNJaFfNeNPzzFf +mWGhITApJfzW9f+ZcC+NBRfX2Pz62qL3vWwpocTrVanG8RUqaAUIDpUr+Da1nQSL +gEEIB6NHDhlTFYOTjNsLGPChGmGIQfpheOe6+ooKYTOjPJ1AZ43SZGR0y7rFfpxO +sH6We/jov7GbMSj6uUQALuJwAU3Ct1igmUJ1rvvv9zvQmpZkV5JiP+bQPEvrMAra +QLCamQXhA2oyK2dddsUFEnZ4FunXUSrCODl0LsXdu9FBAgMBAAGjUzBRMB0GA1Ud +DgQWBBSV7EHluzoN45UEXZaa8y3+6cBeETAfBgNVHSMEGDAWgBSV7EHluzoN45UE +XZaa8y3+6cBeETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA+ +6hX9+1ncRJwR4zz3e/dN3rs8Q6xtFOuft8oXMS8DJgaJpjnnP/bRDg8+qozjtlai +5b63V9NKr/iaKWLlEC6z+nY7xhAsETyN2By6tVPy1DrKaTdWvUzM3fG5nEquUvmh +W/IXexXzHPe/L8FfZDD80EoEzM73D5GyhQtX488npULYyyPJTAfR5q4CHzbubq9m +kfOif6MH4s0sgDZkcPFNt5dTKj7OUIbVljINEcd6VP+I6m1Wb4vYOBttqQ9jLgLm +8hc09omOX3/qlRvQuQbdGNaAwRLE8DNtdR9rFIG3Gm2MZaVwDwQnRaeF+uKcFL9u +MRIs49xwswJtg+AFGO6D +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDCzCCAfOgAwIBAgIUYm8J65XSPCtliio0zuVRkze8ke8wDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKdGVzdC5sb2NhbDAeFw0yNjA3MTcxMDQ1MjBaFw0yNzA3 +MTcxMDQ1MjBaMBUxEzARBgNVBAMMCnRlc3QubG9jYWwwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDEY4OqEKrlPu48ZgR60aKBcU6hONfoUO2HBm5FP76V +zkiSFkZlPL5FkZMUQ5PWTFuM74WasxGo5CWDpwexDczCKwBWnYCNJaFfNeNPzzFf +mWGhITApJfzW9f+ZcC+NBRfX2Pz62qL3vWwpocTrVanG8RUqaAUIDpUr+Da1nQSL +gEEIB6NHDhlTFYOTjNsLGPChGmGIQfpheOe6+ooKYTOjPJ1AZ43SZGR0y7rFfpxO +sH6We/jov7GbMSj6uUQALuJwAU3Ct1igmUJ1rvvv9zvQmpZkV5JiP+bQPEvrMAra +QLCamQXhA2oyK2dddsUFEnZ4FunXUSrCODl0LsXdu9FBAgMBAAGjUzBRMB0GA1Ud +DgQWBBSV7EHluzoN45UEXZaa8y3+6cBeETAfBgNVHSMEGDAWgBSV7EHluzoN45UE +XZaa8y3+6cBeETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA+ +6hX9+1ncRJwR4zz3e/dN3rs8Q6xtFOuft8oXMS8DJgaJpjnnP/bRDg8+qozjtlai +5b63V9NKr/iaKWLlEC6z+nY7xhAsETyN2By6tVPy1DrKaTdWvUzM3fG5nEquUvmh +W/IXexXzHPe/L8FfZDD80EoEzM73D5GyhQtX488npULYyyPJTAfR5q4CHzbubq9m +kfOif6MH4s0sgDZkcPFNt5dTKj7OUIbVljINEcd6VP+I6m1Wb4vYOBttqQ9jLgLm +8hc09omOX3/qlRvQuQbdGNaAwRLE8DNtdR9rFIG3Gm2MZaVwDwQnRaeF+uKcFL9u +MRIs49xwswJtg+AFGO6D +-----END CERTIFICATE----- diff --git a/src/test/resources/test-ca.pem b/src/test/resources/test-ca.pem new file mode 100644 index 0000000..9ff29bb --- /dev/null +++ b/src/test/resources/test-ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCzCCAfOgAwIBAgIUYm8J65XSPCtliio0zuVRkze8ke8wDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKdGVzdC5sb2NhbDAeFw0yNjA3MTcxMDQ1MjBaFw0yNzA3 +MTcxMDQ1MjBaMBUxEzARBgNVBAMMCnRlc3QubG9jYWwwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDEY4OqEKrlPu48ZgR60aKBcU6hONfoUO2HBm5FP76V +zkiSFkZlPL5FkZMUQ5PWTFuM74WasxGo5CWDpwexDczCKwBWnYCNJaFfNeNPzzFf +mWGhITApJfzW9f+ZcC+NBRfX2Pz62qL3vWwpocTrVanG8RUqaAUIDpUr+Da1nQSL +gEEIB6NHDhlTFYOTjNsLGPChGmGIQfpheOe6+ooKYTOjPJ1AZ43SZGR0y7rFfpxO +sH6We/jov7GbMSj6uUQALuJwAU3Ct1igmUJ1rvvv9zvQmpZkV5JiP+bQPEvrMAra +QLCamQXhA2oyK2dddsUFEnZ4FunXUSrCODl0LsXdu9FBAgMBAAGjUzBRMB0GA1Ud +DgQWBBSV7EHluzoN45UEXZaa8y3+6cBeETAfBgNVHSMEGDAWgBSV7EHluzoN45UE +XZaa8y3+6cBeETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA+ +6hX9+1ncRJwR4zz3e/dN3rs8Q6xtFOuft8oXMS8DJgaJpjnnP/bRDg8+qozjtlai +5b63V9NKr/iaKWLlEC6z+nY7xhAsETyN2By6tVPy1DrKaTdWvUzM3fG5nEquUvmh +W/IXexXzHPe/L8FfZDD80EoEzM73D5GyhQtX488npULYyyPJTAfR5q4CHzbubq9m +kfOif6MH4s0sgDZkcPFNt5dTKj7OUIbVljINEcd6VP+I6m1Wb4vYOBttqQ9jLgLm +8hc09omOX3/qlRvQuQbdGNaAwRLE8DNtdR9rFIG3Gm2MZaVwDwQnRaeF+uKcFL9u +MRIs49xwswJtg+AFGO6D +-----END CERTIFICATE----- From a020c7ec1bf96b00373e7bb1864fdd363787287d Mon Sep 17 00:00:00 2001 From: "yndu13(agent)" Date: Mon, 20 Jul 2026 20:33:27 +0800 Subject: [PATCH 2/2] Avoid committing static test CA PEMs; generate ephemeral self-signed certs in tests. --- .../tea/okhttp/OkHttpClientBuilderTest.java | 107 ++++++++++++++++-- src/test/resources/test-ca-dual.pem | 38 ------- src/test/resources/test-ca.pem | 19 ---- 3 files changed, 96 insertions(+), 68 deletions(-) delete mode 100644 src/test/resources/test-ca-dual.pem delete mode 100644 src/test/resources/test-ca.pem diff --git a/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java b/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java index baea179..2e6cc3c 100644 --- a/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java +++ b/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java @@ -11,22 +11,103 @@ import org.junit.Test; import org.mockito.Mockito; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; -import java.util.Scanner; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; public class OkHttpClientBuilderTest { private Map map = new HashMap<>(); - private static String readResource(String name) { - InputStream is = OkHttpClientBuilderTest.class.getClassLoader().getResourceAsStream(name); - Assert.assertNotNull(is); - try (Scanner scanner = new Scanner(is, StandardCharsets.UTF_8.name())) { - scanner.useDelimiter("\\A"); - return scanner.hasNext() ? scanner.next() : ""; + /** + * Build an ephemeral self-signed CA PEM at runtime (CN under *.local / UnitTestFixture). + * No production certificates or private keys are committed to the repository. + */ + private static String createEphemeralSelfSignedCaPem(String commonName) throws Exception { + Path workDir = Files.createTempDirectory("tea-java-test-ca-"); + Path keystore = workDir.resolve("test-ca.p12"); + Path pemFile = workDir.resolve("test-ca.pem"); + try { + Path keytool = resolveKeytool(); + runKeytool(keytool, + "-genkeypair", + "-alias", "testca", + "-keyalg", "RSA", + "-keysize", "2048", + "-validity", "1", + "-dname", "CN=" + commonName + ",OU=UnitTestFixture,O=AliyunTeaJavaTest,C=CN", + "-keystore", keystore.toString(), + "-storetype", "PKCS12", + "-storepass", "changeit", + "-keypass", "changeit", + "-noprompt"); + runKeytool(keytool, + "-exportcert", + "-alias", "testca", + "-keystore", keystore.toString(), + "-storetype", "PKCS12", + "-storepass", "changeit", + "-rfc", + "-file", pemFile.toString()); + byte[] pemBytes = Files.readAllBytes(pemFile); + return new String(pemBytes, StandardCharsets.US_ASCII); + } finally { + deleteRecursively(workDir); + } + } + + private static Path resolveKeytool() { + Path keytool = java.nio.file.Paths.get(System.getProperty("java.home"), "bin", "keytool"); + if (!Files.isExecutable(keytool)) { + keytool = java.nio.file.Paths.get(System.getProperty("java.home"), "bin", "keytool.exe"); + } + Assert.assertTrue("keytool not found under java.home", Files.isExecutable(keytool)); + return keytool; + } + + private static void runKeytool(Path keytool, String... args) throws Exception { + String[] command = new String[args.length + 1]; + command[0] = keytool.toString(); + System.arraycopy(args, 0, command, 1, args.length); + Process process = new ProcessBuilder(command) + .redirectErrorStream(true) + .start(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (InputStream in = process.getInputStream()) { + byte[] buf = new byte[1024]; + int n; + while ((n = in.read(buf)) >= 0) { + output.write(buf, 0, n); + } + } + if (!process.waitFor(60, TimeUnit.SECONDS)) { + process.destroyForcibly(); + Assert.fail("keytool timed out"); + } + if (process.exitValue() != 0) { + Assert.fail("keytool failed: " + new String(output.toByteArray(), StandardCharsets.UTF_8)); + } + } + + private static void deleteRecursively(Path root) throws IOException { + if (!Files.exists(root)) { + return; + } + try (Stream walk = Files.walk(root)) { + walk.sorted(Comparator.reverseOrder()).forEach(path -> { + try { + Files.deleteIfExists(path); + } catch (IOException ignored) { + // best-effort cleanup of ephemeral fixtures + } + }); } } @@ -135,7 +216,7 @@ public void connectionPoolTest() { } @Test - public void certificateTest() throws IOException { + public void certificateTest() throws Exception { map.clear(); OkHttpClientBuilder clientBuilder = Mockito.spy(new OkHttpClientBuilder()); clientBuilder.certificate(map); @@ -192,14 +273,18 @@ public void certificateTest() throws IOException { Assert.assertTrue(e instanceof TeaException); } - // valid CA PEM covers split + trust store path without requiring CI CA secret + // ephemeral self-signed fixtures only — covers split + trust store without committed PEMs / CI CA secret map.clear(); map.put("ignoreSSL", false); - map.put("ca", readResource("test-ca.pem")); + String singleCa = createEphemeralSelfSignedCaPem("tea-java-unit-test.local"); + map.put("ca", singleCa); OkHttpClient clientWithCa = new OkHttpClientBuilder().certificate(map).buildOkHttpClient(); Assert.assertNotNull(clientWithCa.sslSocketFactory()); - map.put("ca", readResource("test-ca-dual.pem")); + String dualCa = createEphemeralSelfSignedCaPem("tea-java-unit-test-a.local") + + "\n" + + createEphemeralSelfSignedCaPem("tea-java-unit-test-b.local"); + map.put("ca", dualCa); Assert.assertNotNull(new OkHttpClientBuilder().certificate(map).buildOkHttpClient().sslSocketFactory()); // non-PEM CA still enters splitPemCertificates else-branch diff --git a/src/test/resources/test-ca-dual.pem b/src/test/resources/test-ca-dual.pem deleted file mode 100644 index 03488a7..0000000 --- a/src/test/resources/test-ca-dual.pem +++ /dev/null @@ -1,38 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDCzCCAfOgAwIBAgIUYm8J65XSPCtliio0zuVRkze8ke8wDQYJKoZIhvcNAQEL -BQAwFTETMBEGA1UEAwwKdGVzdC5sb2NhbDAeFw0yNjA3MTcxMDQ1MjBaFw0yNzA3 -MTcxMDQ1MjBaMBUxEzARBgNVBAMMCnRlc3QubG9jYWwwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDEY4OqEKrlPu48ZgR60aKBcU6hONfoUO2HBm5FP76V -zkiSFkZlPL5FkZMUQ5PWTFuM74WasxGo5CWDpwexDczCKwBWnYCNJaFfNeNPzzFf -mWGhITApJfzW9f+ZcC+NBRfX2Pz62qL3vWwpocTrVanG8RUqaAUIDpUr+Da1nQSL -gEEIB6NHDhlTFYOTjNsLGPChGmGIQfpheOe6+ooKYTOjPJ1AZ43SZGR0y7rFfpxO -sH6We/jov7GbMSj6uUQALuJwAU3Ct1igmUJ1rvvv9zvQmpZkV5JiP+bQPEvrMAra -QLCamQXhA2oyK2dddsUFEnZ4FunXUSrCODl0LsXdu9FBAgMBAAGjUzBRMB0GA1Ud -DgQWBBSV7EHluzoN45UEXZaa8y3+6cBeETAfBgNVHSMEGDAWgBSV7EHluzoN45UE -XZaa8y3+6cBeETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA+ -6hX9+1ncRJwR4zz3e/dN3rs8Q6xtFOuft8oXMS8DJgaJpjnnP/bRDg8+qozjtlai -5b63V9NKr/iaKWLlEC6z+nY7xhAsETyN2By6tVPy1DrKaTdWvUzM3fG5nEquUvmh -W/IXexXzHPe/L8FfZDD80EoEzM73D5GyhQtX488npULYyyPJTAfR5q4CHzbubq9m -kfOif6MH4s0sgDZkcPFNt5dTKj7OUIbVljINEcd6VP+I6m1Wb4vYOBttqQ9jLgLm -8hc09omOX3/qlRvQuQbdGNaAwRLE8DNtdR9rFIG3Gm2MZaVwDwQnRaeF+uKcFL9u -MRIs49xwswJtg+AFGO6D ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDCzCCAfOgAwIBAgIUYm8J65XSPCtliio0zuVRkze8ke8wDQYJKoZIhvcNAQEL -BQAwFTETMBEGA1UEAwwKdGVzdC5sb2NhbDAeFw0yNjA3MTcxMDQ1MjBaFw0yNzA3 -MTcxMDQ1MjBaMBUxEzARBgNVBAMMCnRlc3QubG9jYWwwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDEY4OqEKrlPu48ZgR60aKBcU6hONfoUO2HBm5FP76V -zkiSFkZlPL5FkZMUQ5PWTFuM74WasxGo5CWDpwexDczCKwBWnYCNJaFfNeNPzzFf -mWGhITApJfzW9f+ZcC+NBRfX2Pz62qL3vWwpocTrVanG8RUqaAUIDpUr+Da1nQSL -gEEIB6NHDhlTFYOTjNsLGPChGmGIQfpheOe6+ooKYTOjPJ1AZ43SZGR0y7rFfpxO -sH6We/jov7GbMSj6uUQALuJwAU3Ct1igmUJ1rvvv9zvQmpZkV5JiP+bQPEvrMAra -QLCamQXhA2oyK2dddsUFEnZ4FunXUSrCODl0LsXdu9FBAgMBAAGjUzBRMB0GA1Ud -DgQWBBSV7EHluzoN45UEXZaa8y3+6cBeETAfBgNVHSMEGDAWgBSV7EHluzoN45UE -XZaa8y3+6cBeETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA+ -6hX9+1ncRJwR4zz3e/dN3rs8Q6xtFOuft8oXMS8DJgaJpjnnP/bRDg8+qozjtlai -5b63V9NKr/iaKWLlEC6z+nY7xhAsETyN2By6tVPy1DrKaTdWvUzM3fG5nEquUvmh -W/IXexXzHPe/L8FfZDD80EoEzM73D5GyhQtX488npULYyyPJTAfR5q4CHzbubq9m -kfOif6MH4s0sgDZkcPFNt5dTKj7OUIbVljINEcd6VP+I6m1Wb4vYOBttqQ9jLgLm -8hc09omOX3/qlRvQuQbdGNaAwRLE8DNtdR9rFIG3Gm2MZaVwDwQnRaeF+uKcFL9u -MRIs49xwswJtg+AFGO6D ------END CERTIFICATE----- diff --git a/src/test/resources/test-ca.pem b/src/test/resources/test-ca.pem deleted file mode 100644 index 9ff29bb..0000000 --- a/src/test/resources/test-ca.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDCzCCAfOgAwIBAgIUYm8J65XSPCtliio0zuVRkze8ke8wDQYJKoZIhvcNAQEL -BQAwFTETMBEGA1UEAwwKdGVzdC5sb2NhbDAeFw0yNjA3MTcxMDQ1MjBaFw0yNzA3 -MTcxMDQ1MjBaMBUxEzARBgNVBAMMCnRlc3QubG9jYWwwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDEY4OqEKrlPu48ZgR60aKBcU6hONfoUO2HBm5FP76V -zkiSFkZlPL5FkZMUQ5PWTFuM74WasxGo5CWDpwexDczCKwBWnYCNJaFfNeNPzzFf -mWGhITApJfzW9f+ZcC+NBRfX2Pz62qL3vWwpocTrVanG8RUqaAUIDpUr+Da1nQSL -gEEIB6NHDhlTFYOTjNsLGPChGmGIQfpheOe6+ooKYTOjPJ1AZ43SZGR0y7rFfpxO -sH6We/jov7GbMSj6uUQALuJwAU3Ct1igmUJ1rvvv9zvQmpZkV5JiP+bQPEvrMAra -QLCamQXhA2oyK2dddsUFEnZ4FunXUSrCODl0LsXdu9FBAgMBAAGjUzBRMB0GA1Ud -DgQWBBSV7EHluzoN45UEXZaa8y3+6cBeETAfBgNVHSMEGDAWgBSV7EHluzoN45UE -XZaa8y3+6cBeETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA+ -6hX9+1ncRJwR4zz3e/dN3rs8Q6xtFOuft8oXMS8DJgaJpjnnP/bRDg8+qozjtlai -5b63V9NKr/iaKWLlEC6z+nY7xhAsETyN2By6tVPy1DrKaTdWvUzM3fG5nEquUvmh -W/IXexXzHPe/L8FfZDD80EoEzM73D5GyhQtX488npULYyyPJTAfR5q4CHzbubq9m -kfOif6MH4s0sgDZkcPFNt5dTKj7OUIbVljINEcd6VP+I6m1Wb4vYOBttqQ9jLgLm -8hc09omOX3/qlRvQuQbdGNaAwRLE8DNtdR9rFIG3Gm2MZaVwDwQnRaeF+uKcFL9u -MRIs49xwswJtg+AFGO6D ------END CERTIFICATE-----