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..2e6cc3c 100644 --- a/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java +++ b/src/test/java/com/aliyun/tea/okhttp/OkHttpClientBuilderTest.java @@ -4,17 +4,113 @@ 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.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.concurrent.TimeUnit; +import java.util.stream.Stream; public class OkHttpClientBuilderTest { private Map map = new HashMap<>(); + /** + * 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 + } + }); + } + } + @Test public void timeOutTest() { map.clear(); @@ -120,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); @@ -176,6 +272,71 @@ public void certificateTest() throws IOException { } catch (Exception e) { Assert.assertTrue(e instanceof TeaException); } + + // ephemeral self-signed fixtures only — covers split + trust store without committed PEMs / CI CA secret + map.clear(); + map.put("ignoreSSL", false); + String singleCa = createEphemeralSelfSignedCaPem("tea-java-unit-test.local"); + map.put("ca", singleCa); + OkHttpClient clientWithCa = new OkHttpClientBuilder().certificate(map).buildOkHttpClient(); + Assert.assertNotNull(clientWithCa.sslSocketFactory()); + + 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 + 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) {