From 4bb413bf57fbeaeeed336b9595a2af420d4dc4b1 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Tue, 26 May 2026 14:35:07 +0200 Subject: [PATCH 1/5] test: add gzip regression tests and invariant comment --- build.gradle.kts | 1 + .../services/framework/StreamHTTPClient.java | 4 + src/test/java/io/getstream/GzipTest.java | 129 ++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 src/test/java/io/getstream/GzipTest.java diff --git a/build.gradle.kts b/build.gradle.kts index 606bccb3..7b9a9062 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.2") testImplementation("org.apache.commons:commons-lang3:3.18.0") + testImplementation("com.squareup.okhttp3:mockwebserver") compileOnly("org.projectlombok:lombok:1.18.32") annotationProcessor("org.projectlombok:lombok:1.18.32") testCompileOnly("org.projectlombok:lombok:1.18.32") diff --git a/src/main/java/io/getstream/services/framework/StreamHTTPClient.java b/src/main/java/io/getstream/services/framework/StreamHTTPClient.java index 69df4fe3..094470ac 100644 --- a/src/main/java/io/getstream/services/framework/StreamHTTPClient.java +++ b/src/main/java/io/getstream/services/framework/StreamHTTPClient.java @@ -186,6 +186,10 @@ private void readPropertiesAndEnv(Properties properties) { private OkHttpClient buildHTTPClient(String jwtToken, OkHttpClient.Builder httpClient) { httpClient.interceptors().clear(); + // CHA-2964 invariant: do NOT add a custom interceptor that sets + // "Accept-Encoding". OkHttp's BridgeInterceptor (default) auto-adds + // "Accept-Encoding: gzip" and auto-decodes the response. Setting it + // manually disables that auto-handling. HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor().setLevel(getLogLevel()); httpClient.addInterceptor(loggingInterceptor); diff --git a/src/test/java/io/getstream/GzipTest.java b/src/test/java/io/getstream/GzipTest.java new file mode 100644 index 00000000..db0b38a3 --- /dev/null +++ b/src/test/java/io/getstream/GzipTest.java @@ -0,0 +1,129 @@ +package io.getstream; + +import static org.junit.jupiter.api.Assertions.*; + +import io.getstream.models.GetApplicationResponse; +import io.getstream.services.framework.StreamHTTPClient; +import io.getstream.services.framework.StreamSDKClient; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.zip.GZIPOutputStream; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import okio.Buffer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * CHA-2964: verifies the SDK relies on OkHttp's BridgeInterceptor to auto-negotiate gzip + * compression for responses. The SDK does NOT set Accept-Encoding manually; doing so would disable + * OkHttp's transparent gzip decoding. + */ +public class GzipTest { + + // HS256 requires a secret of at least 32 bytes. + private static final String TEST_API_KEY = "test-api-key"; + private static final String TEST_API_SECRET = "test-api-secret-must-be-32-bytes-long"; + + private MockWebServer server; + private StreamSDKClient client; + + // Snapshot original system properties so other tests/CI runs aren't affected. + private String originalUrl; + private String originalApiKey; + private String originalApiSecret; + + @BeforeEach + void setUp() throws Exception { + server = new MockWebServer(); + server.start(); + + originalUrl = System.getProperty(StreamHTTPClient.API_URL_PROP_NAME); + originalApiKey = System.getProperty(StreamHTTPClient.API_KEY_PROP_NAME); + originalApiSecret = System.getProperty(StreamHTTPClient.API_SECRET_PROP_NAME); + + String baseUrl = server.url("/").toString(); + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + System.setProperty(StreamHTTPClient.API_URL_PROP_NAME, baseUrl); + System.setProperty(StreamHTTPClient.API_KEY_PROP_NAME, TEST_API_KEY); + System.setProperty(StreamHTTPClient.API_SECRET_PROP_NAME, TEST_API_SECRET); + + client = new StreamSDKClient(new StreamHTTPClient()); + } + + @AfterEach + void tearDown() throws Exception { + restoreProperty(StreamHTTPClient.API_URL_PROP_NAME, originalUrl); + restoreProperty(StreamHTTPClient.API_KEY_PROP_NAME, originalApiKey); + restoreProperty(StreamHTTPClient.API_SECRET_PROP_NAME, originalApiSecret); + if (server != null) { + server.shutdown(); + } + } + + private static void restoreProperty(String key, String previousValue) { + if (previousValue == null) { + System.clearProperty(key); + } else { + System.setProperty(key, previousValue); + } + } + + /** + * Verifies the SDK's HTTP client advertises gzip support on outgoing requests. OkHttp's + * BridgeInterceptor auto-adds {@code Accept-Encoding: gzip} when no upstream interceptor sets it. + */ + @Test + public void testRequestAdvertisesGzip() throws Exception { + // Respond with plain JSON; the request is what we care about here. + server.enqueue( + new MockResponse() + .setResponseCode(200) + .setHeader("Content-Type", "application/json") + .setBody("{\"duration\":\"1ms\",\"app\":{\"name\":\"test-app\"}}")); + + GetApplicationResponse resp = client.getApp().execute().getData(); + assertNotNull(resp); + + RecordedRequest recorded = server.takeRequest(); + String acceptEncoding = recorded.getHeader("Accept-Encoding"); + assertNotNull( + acceptEncoding, "Accept-Encoding header must be present on outgoing SDK requests"); + assertTrue( + acceptEncoding.toLowerCase().contains("gzip"), + "Accept-Encoding header must advertise gzip, got: " + acceptEncoding); + } + + /** + * Verifies the SDK transparently decodes a gzip-encoded response body. OkHttp's BridgeInterceptor + * inflates the body when it sees {@code Content-Encoding: gzip} and the request didn't manually + * set Accept-Encoding. + */ + @Test + public void testResponseGzipDecoded() throws Exception { + String jsonBody = "{\"duration\":\"2ms\",\"app\":{\"name\":\"gzipped-app\"}}"; + + ByteArrayOutputStream gzippedBytes = new ByteArrayOutputStream(); + try (GZIPOutputStream gz = new GZIPOutputStream(gzippedBytes)) { + gz.write(jsonBody.getBytes(StandardCharsets.UTF_8)); + } + + Buffer body = new Buffer().write(gzippedBytes.toByteArray()); + server.enqueue( + new MockResponse() + .setResponseCode(200) + .setHeader("Content-Type", "application/json") + .setHeader("Content-Encoding", "gzip") + .setBody(body)); + + GetApplicationResponse resp = client.getApp().execute().getData(); + assertNotNull(resp, "SDK must deserialize the gzip-decoded response"); + assertEquals("2ms", resp.getDuration()); + assertNotNull(resp.getApp(), "Nested app payload must be present after gzip decode"); + assertEquals("gzipped-app", resp.getApp().getName()); + } +} From 269ba33b968380ec2f1576c1987b68207ebc383c Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Tue, 26 May 2026 15:51:15 +0200 Subject: [PATCH 2/5] test: bypass env-aware URL resolution in GzipTest CI sets STREAM_BASE_URL which wins over System.setProperty, so the previous test hit the real Stream API and asserted on a duration field populated with live wall-clock latency (672.90ms vs expected 2ms from mock). Rewrites the test to hit the SDK-configured OkHttpClient directly against MockWebServer, which is the actual invariant the spec cares about. --- src/test/java/io/getstream/GzipTest.java | 88 +++++++----------------- 1 file changed, 26 insertions(+), 62 deletions(-) diff --git a/src/test/java/io/getstream/GzipTest.java b/src/test/java/io/getstream/GzipTest.java index db0b38a3..91481606 100644 --- a/src/test/java/io/getstream/GzipTest.java +++ b/src/test/java/io/getstream/GzipTest.java @@ -2,12 +2,14 @@ import static org.junit.jupiter.api.Assertions.*; -import io.getstream.models.GetApplicationResponse; -import io.getstream.services.framework.StreamHTTPClient; import io.getstream.services.framework.StreamSDKClient; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.zip.GZIPOutputStream; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -17,95 +19,56 @@ import org.junit.jupiter.api.Test; /** - * CHA-2964: verifies the SDK relies on OkHttp's BridgeInterceptor to auto-negotiate gzip - * compression for responses. The SDK does NOT set Accept-Encoding manually; doing so would disable - * OkHttp's transparent gzip decoding. + * CHA-2964: verifies the SDK relies on OkHttp's BridgeInterceptor to auto-negotiate gzip. Hits the + * SDK-configured OkHttpClient directly against a MockWebServer so the test does not depend on URL + * resolution (env var STREAM_BASE_URL otherwise wins over System.setProperty on CI). */ public class GzipTest { - // HS256 requires a secret of at least 32 bytes. private static final String TEST_API_KEY = "test-api-key"; private static final String TEST_API_SECRET = "test-api-secret-must-be-32-bytes-long"; private MockWebServer server; - private StreamSDKClient client; - - // Snapshot original system properties so other tests/CI runs aren't affected. - private String originalUrl; - private String originalApiKey; - private String originalApiSecret; + private OkHttpClient sdkHttpClient; @BeforeEach void setUp() throws Exception { server = new MockWebServer(); server.start(); - - originalUrl = System.getProperty(StreamHTTPClient.API_URL_PROP_NAME); - originalApiKey = System.getProperty(StreamHTTPClient.API_KEY_PROP_NAME); - originalApiSecret = System.getProperty(StreamHTTPClient.API_SECRET_PROP_NAME); - - String baseUrl = server.url("/").toString(); - if (baseUrl.endsWith("/")) { - baseUrl = baseUrl.substring(0, baseUrl.length() - 1); - } - System.setProperty(StreamHTTPClient.API_URL_PROP_NAME, baseUrl); - System.setProperty(StreamHTTPClient.API_KEY_PROP_NAME, TEST_API_KEY); - System.setProperty(StreamHTTPClient.API_SECRET_PROP_NAME, TEST_API_SECRET); - - client = new StreamSDKClient(new StreamHTTPClient()); + StreamSDKClient sdk = new StreamSDKClient(TEST_API_KEY, TEST_API_SECRET); + sdkHttpClient = sdk.getHttpClient().getHttpClient(); } @AfterEach void tearDown() throws Exception { - restoreProperty(StreamHTTPClient.API_URL_PROP_NAME, originalUrl); - restoreProperty(StreamHTTPClient.API_KEY_PROP_NAME, originalApiKey); - restoreProperty(StreamHTTPClient.API_SECRET_PROP_NAME, originalApiSecret); if (server != null) { server.shutdown(); } } - private static void restoreProperty(String key, String previousValue) { - if (previousValue == null) { - System.clearProperty(key); - } else { - System.setProperty(key, previousValue); - } - } - - /** - * Verifies the SDK's HTTP client advertises gzip support on outgoing requests. OkHttp's - * BridgeInterceptor auto-adds {@code Accept-Encoding: gzip} when no upstream interceptor sets it. - */ @Test public void testRequestAdvertisesGzip() throws Exception { - // Respond with plain JSON; the request is what we care about here. - server.enqueue( - new MockResponse() - .setResponseCode(200) - .setHeader("Content-Type", "application/json") - .setBody("{\"duration\":\"1ms\",\"app\":{\"name\":\"test-app\"}}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); - GetApplicationResponse resp = client.getApp().execute().getData(); - assertNotNull(resp); + HttpUrl url = server.url("/test"); + Request request = new Request.Builder().url(url).get().build(); + try (Response resp = sdkHttpClient.newCall(request).execute()) { + assertTrue(resp.isSuccessful()); + } RecordedRequest recorded = server.takeRequest(); String acceptEncoding = recorded.getHeader("Accept-Encoding"); assertNotNull( - acceptEncoding, "Accept-Encoding header must be present on outgoing SDK requests"); + acceptEncoding, + "OkHttp's BridgeInterceptor must add Accept-Encoding when no upstream interceptor sets it"); assertTrue( acceptEncoding.toLowerCase().contains("gzip"), - "Accept-Encoding header must advertise gzip, got: " + acceptEncoding); + "Accept-Encoding must advertise gzip, got: " + acceptEncoding); } - /** - * Verifies the SDK transparently decodes a gzip-encoded response body. OkHttp's BridgeInterceptor - * inflates the body when it sees {@code Content-Encoding: gzip} and the request didn't manually - * set Accept-Encoding. - */ @Test public void testResponseGzipDecoded() throws Exception { - String jsonBody = "{\"duration\":\"2ms\",\"app\":{\"name\":\"gzipped-app\"}}"; + String jsonBody = "{\"hello\":\"world\"}"; ByteArrayOutputStream gzippedBytes = new ByteArrayOutputStream(); try (GZIPOutputStream gz = new GZIPOutputStream(gzippedBytes)) { @@ -120,10 +83,11 @@ public void testResponseGzipDecoded() throws Exception { .setHeader("Content-Encoding", "gzip") .setBody(body)); - GetApplicationResponse resp = client.getApp().execute().getData(); - assertNotNull(resp, "SDK must deserialize the gzip-decoded response"); - assertEquals("2ms", resp.getDuration()); - assertNotNull(resp.getApp(), "Nested app payload must be present after gzip decode"); - assertEquals("gzipped-app", resp.getApp().getName()); + HttpUrl url = server.url("/test"); + Request request = new Request.Builder().url(url).get().build(); + try (Response resp = sdkHttpClient.newCall(request).execute()) { + String decoded = resp.body().string(); + assertEquals(jsonBody, decoded, "OkHttp must transparently decode the gzip response"); + } } } From c79a0d2fba306b65a8c69ee1bd498a29b3d2349e Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Tue, 26 May 2026 16:04:23 +0200 Subject: [PATCH 3/5] docs: trim gzip invariant comment --- .../io/getstream/services/framework/StreamHTTPClient.java | 6 ++---- src/test/java/io/getstream/GzipTest.java | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/getstream/services/framework/StreamHTTPClient.java b/src/main/java/io/getstream/services/framework/StreamHTTPClient.java index 094470ac..724d96f9 100644 --- a/src/main/java/io/getstream/services/framework/StreamHTTPClient.java +++ b/src/main/java/io/getstream/services/framework/StreamHTTPClient.java @@ -186,10 +186,8 @@ private void readPropertiesAndEnv(Properties properties) { private OkHttpClient buildHTTPClient(String jwtToken, OkHttpClient.Builder httpClient) { httpClient.interceptors().clear(); - // CHA-2964 invariant: do NOT add a custom interceptor that sets - // "Accept-Encoding". OkHttp's BridgeInterceptor (default) auto-adds - // "Accept-Encoding: gzip" and auto-decodes the response. Setting it - // manually disables that auto-handling. + // Don't add an interceptor that sets Accept-Encoding; it disables + // OkHttp's automatic gzip handling. HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor().setLevel(getLogLevel()); httpClient.addInterceptor(loggingInterceptor); diff --git a/src/test/java/io/getstream/GzipTest.java b/src/test/java/io/getstream/GzipTest.java index 91481606..6f31afc2 100644 --- a/src/test/java/io/getstream/GzipTest.java +++ b/src/test/java/io/getstream/GzipTest.java @@ -19,9 +19,8 @@ import org.junit.jupiter.api.Test; /** - * CHA-2964: verifies the SDK relies on OkHttp's BridgeInterceptor to auto-negotiate gzip. Hits the - * SDK-configured OkHttpClient directly against a MockWebServer so the test does not depend on URL - * resolution (env var STREAM_BASE_URL otherwise wins over System.setProperty on CI). + * Verifies the SDK relies on OkHttp's BridgeInterceptor to auto-negotiate gzip, + * by exercising the SDK's OkHttpClient directly against a MockWebServer. */ public class GzipTest { From b9fec0dd1bdbce869c95fe45f6b8129d6bf8c606 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Tue, 26 May 2026 16:43:36 +0200 Subject: [PATCH 4/5] docs: drop gzip warning comment, test covers invariant --- .../java/io/getstream/services/framework/StreamHTTPClient.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/io/getstream/services/framework/StreamHTTPClient.java b/src/main/java/io/getstream/services/framework/StreamHTTPClient.java index 724d96f9..69df4fe3 100644 --- a/src/main/java/io/getstream/services/framework/StreamHTTPClient.java +++ b/src/main/java/io/getstream/services/framework/StreamHTTPClient.java @@ -186,8 +186,6 @@ private void readPropertiesAndEnv(Properties properties) { private OkHttpClient buildHTTPClient(String jwtToken, OkHttpClient.Builder httpClient) { httpClient.interceptors().clear(); - // Don't add an interceptor that sets Accept-Encoding; it disables - // OkHttp's automatic gzip handling. HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor().setLevel(getLogLevel()); httpClient.addInterceptor(loggingInterceptor); From a92296532e9d7480a534fa14b7855003a2a46834 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Tue, 26 May 2026 16:49:32 +0200 Subject: [PATCH 5/5] style: spotless reformat --- src/test/java/io/getstream/GzipTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/getstream/GzipTest.java b/src/test/java/io/getstream/GzipTest.java index 6f31afc2..999e39b2 100644 --- a/src/test/java/io/getstream/GzipTest.java +++ b/src/test/java/io/getstream/GzipTest.java @@ -19,8 +19,8 @@ import org.junit.jupiter.api.Test; /** - * Verifies the SDK relies on OkHttp's BridgeInterceptor to auto-negotiate gzip, - * by exercising the SDK's OkHttpClient directly against a MockWebServer. + * Verifies the SDK relies on OkHttp's BridgeInterceptor to auto-negotiate gzip, by exercising the + * SDK's OkHttpClient directly against a MockWebServer. */ public class GzipTest {