From a9daeedd02e551442afa6af62e8518756ef07d71 Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Fri, 29 May 2026 14:30:11 +0200 Subject: [PATCH 1/7] Enhance logging for fetch errors by including CF-RAY header value --- .../com/configcat/ConfigCatLogMessages.java | 21 ++++++++++-- .../java/com/configcat/ConfigFetcher.java | 33 +++++++++++-------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/configcat/ConfigCatLogMessages.java b/src/main/java/com/configcat/ConfigCatLogMessages.java index 2198981..82a58c2 100644 --- a/src/main/java/com/configcat/ConfigCatLogMessages.java +++ b/src/main/java/com/configcat/ConfigCatLogMessages.java @@ -26,7 +26,7 @@ final class ConfigCatLogMessages { /** * Log message for Fetch Failed Due To Unexpected error. The log eventId is 1103. */ - public static final String FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR = "Unexpected error occurred while trying to fetch config JSON. It is most likely due to a local network issue. Please make sure your application can reach the ConfigCat CDN servers (or your proxy server) over HTTP."; + private static final String FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR = "Unexpected error occurred while trying to fetch config JSON. It is most likely due to a local network issue. Please make sure your application can reach the ConfigCat CDN servers (or your proxy server) over HTTP."; /** * Log message for Fetch Failed Due To Invalid Sdk Key error. The log eventId is 1100. @@ -167,12 +167,29 @@ public static FormattableLogMessage getFetchFailedDueToUnexpectedHttpResponse(fi * * @param connectTimeoutMillis Connect timeout in milliseconds. * @param readTimeoutMillis Read timeout in milliseconds. + * @param cfRayId The http response CF-RAY header value. * @return The formattable log message. */ - public static FormattableLogMessage getFetchFailedDueToRequestTimeout(final Integer connectTimeoutMillis, final Integer readTimeoutMillis) { + public static FormattableLogMessage getFetchFailedDueToRequestTimeout(final Integer connectTimeoutMillis, final Integer readTimeoutMillis, final String cfRayId) { + if (cfRayId != null) { + return new FormattableLogMessage("Request timed out while trying to fetch config JSON. Timeout values: [connect: %dms, read: %dms] %s", connectTimeoutMillis, readTimeoutMillis, ConfigCatLogMessages.getCFRayIdPostFix(cfRayId)); + } return new FormattableLogMessage("Request timed out while trying to fetch config JSON. Timeout values: [connect: %dms, read: %dms]", connectTimeoutMillis, readTimeoutMillis); } + /** + * Log message for Fetch Failed Due To Unexpected error. The log eventId is 1103. + * + * @param cfRayId The http response CF-RAY header value. + * @return The formattable log message. + */ + public static FormattableLogMessage getFetchFailedDueToUnexpectedError(final String cfRayId) { + if (cfRayId != null) { + return new FormattableLogMessage(FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR + " %s", ConfigCatLogMessages.getCFRayIdPostFix(cfRayId)); + } + return new FormattableLogMessage(FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR); + } + /** * Log message for Fetch Failed Due To Redirect Loop error. The log eventId is 1104. * diff --git a/src/main/java/com/configcat/ConfigFetcher.java b/src/main/java/com/configcat/ConfigFetcher.java index 8f0463a..a6bebbd 100644 --- a/src/main/java/com/configcat/ConfigFetcher.java +++ b/src/main/java/com/configcat/ConfigFetcher.java @@ -147,7 +147,7 @@ private CompletableFuture executeFetchAsync(int executionCount, S } } catch (Exception exception) { - this.logger.error(1103, ConfigCatLogMessages.FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR, exception); + this.logger.error(1103, ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(fetchResponse.cfRayId()), exception); return CompletableFuture.completedFuture(fetchResponse); } @@ -165,7 +165,8 @@ private CompletableFuture getResponseAsync(String eTag) { private void callHTTP(String previousETag, CompletableFuture result) { String requestUrl = this.url + "/configuration-files/" + this.sdkKey + "/" + Constants.CONFIG_JSON_NAME; HttpURLConnection urlConnection = null; - + String cfRayId = null; + FetchResponse fetchResponse = null; try { URL fetchUrl = new URL(requestUrl); if (httpOptions.getProxy() != null) { @@ -185,43 +186,47 @@ private void callHTTP(String previousETag, CompletableFuture resu int responseCode = urlConnection.getResponseCode(); Map> responseHeaders = urlConnection.getHeaderFields(); - String cfRayId = readHeaderValue(responseHeaders, "CF-RAY"); + cfRayId = readHeaderValue(responseHeaders, "CF-RAY"); if (responseCode == 200) { String content = readBody(urlConnection.getInputStream()); String eTag = readHeaderValue(responseHeaders,"ETag"); Result configResult = deserializeConfig(content, cfRayId); if (configResult.error() != null) { - result.complete(FetchResponse.failed(configResult.error(), false, cfRayId)); - return; + fetchResponse = FetchResponse.failed(configResult.error(), false, cfRayId); + } else { + logger.debug("Fetch was successful: new config fetched."); + fetchResponse = FetchResponse.fetched(new Entry(configResult.value(), eTag, content, System.currentTimeMillis()), cfRayId); } - logger.debug("Fetch was successful: new config fetched."); - result.complete(FetchResponse.fetched(new Entry(configResult.value(), eTag, content, System.currentTimeMillis()), cfRayId)); } else if (responseCode == 304) { if(cfRayId != null) { logger.debug(String.format("Fetch was successful: config not modified. %s", ConfigCatLogMessages.getCFRayIdPostFix(cfRayId))); } else { logger.debug("Fetch was successful: config not modified."); } - result.complete(FetchResponse.notModified(cfRayId)); + fetchResponse = FetchResponse.notModified(cfRayId); } else if (responseCode == 403 || responseCode == 404) { FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToInvalidSDKKey(cfRayId); logger.error(1100, message); - result.complete(FetchResponse.failed(message, true, cfRayId)); + fetchResponse = FetchResponse.failed(message, true, cfRayId); } else { FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToUnexpectedHttpResponse(responseCode, urlConnection.getResponseMessage(), cfRayId); logger.error(1101, message); - result.complete(FetchResponse.failed(message, false, cfRayId)); + fetchResponse = FetchResponse.failed(message, false, cfRayId); } } catch (SocketTimeoutException e) { - FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpOptions.getConnectTimeoutMillis(), httpOptions.getReadTimeoutMillis()); + FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpOptions.getConnectTimeoutMillis(), httpOptions.getReadTimeoutMillis(), cfRayId); logger.error(1102, message, e); - result.complete(FetchResponse.failed(message, false, null)); + fetchResponse = FetchResponse.failed(message, false, cfRayId); } catch (Exception e) { - String message = ConfigCatLogMessages.FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR; + FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(cfRayId); logger.error(1103, message, e); - result.complete(FetchResponse.failed(message + " " + e.getMessage(), false, null)); + fetchResponse = FetchResponse.failed(message + " " + e.getMessage(), false, cfRayId); } finally { + if(fetchResponse == null) { + fetchResponse = FetchResponse.failed(ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(cfRayId), false, cfRayId); + } + result.complete(fetchResponse); if (urlConnection != null) { urlConnection.disconnect(); } From c1e218c83319d7aabcbc162d007c5a3d5d3f9aab Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Fri, 29 May 2026 14:44:05 +0200 Subject: [PATCH 2/7] Remove unused import in ConfigCatLogMessages.java --- src/main/java/com/configcat/ConfigCatLogMessages.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/configcat/ConfigCatLogMessages.java b/src/main/java/com/configcat/ConfigCatLogMessages.java index 82a58c2..7627e2c 100644 --- a/src/main/java/com/configcat/ConfigCatLogMessages.java +++ b/src/main/java/com/configcat/ConfigCatLogMessages.java @@ -1,6 +1,5 @@ package com.configcat; -import java.util.Iterator; import java.util.Set; From 535e03a31591e1cf6f97c705808e8fee420789e1 Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Fri, 29 May 2026 15:49:39 +0200 Subject: [PATCH 3/7] Update AppStateMonitor to handle receiver export status and bump compileSdk to 34 --- build.gradle | 2 +- src/main/java/com/configcat/AppStateMonitor.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a72c99c..2ff1627 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { - compileSdk 32 + compileSdk 34 minSdk 21 consumerProguardFiles("configcat-proguard-rules.pro") } diff --git a/src/main/java/com/configcat/AppStateMonitor.java b/src/main/java/com/configcat/AppStateMonitor.java index 0016423..9f05e8c 100644 --- a/src/main/java/com/configcat/AppStateMonitor.java +++ b/src/main/java/com/configcat/AppStateMonitor.java @@ -6,6 +6,7 @@ import android.content.res.Configuration; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.os.Build; import android.os.Bundle; import java9.util.function.Consumer; @@ -40,7 +41,11 @@ public AppStateMonitor(Context context, ConfigCatLogger logger) { application.registerComponentCallbacks(this); IntentFilter filter = new IntentFilter(CONNECTIVITY_CHANGE); - application.registerReceiver(this, filter); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + application.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED); + } else { + application.registerReceiver(this, filter); + } } public void addStateChangeListener(Consumer listener) { From 03b75c70ec8d590f7e4578eee03a99f664e896cd Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Sat, 30 May 2026 11:31:46 +0200 Subject: [PATCH 4/7] Improve ConfigCatClient initialization with error handling for IOException --- .../java/com/configcat/ConfigCatClient.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/configcat/ConfigCatClient.java b/src/main/java/com/configcat/ConfigCatClient.java index 0c45087..5dadc79 100644 --- a/src/main/java/com/configcat/ConfigCatClient.java +++ b/src/main/java/com/configcat/ConfigCatClient.java @@ -29,7 +29,7 @@ public final class ConfigCatClient implements ConfigurationProvider { private ConfigService configService; - private ConfigCatClient(String sdkKey, Options options) throws IllegalArgumentException { + private ConfigCatClient(String sdkKey, Options options) throws IllegalArgumentException, IOException { this.logger = new ConfigCatLogger(LoggerFactory.getLogger(ConfigCatClient.class), options.logLevel, options.hooks, options.logFilter); this.clientLogLevel = options.logLevel; @@ -52,8 +52,14 @@ private ConfigCatClient(String sdkKey, Options options) throws IllegalArgumentEx options.isBaseURLCustom(), options.pollingMode.getPollingIdentifier()); - StateMonitor monitor = options.context != null ? new AppStateMonitor(options.context, logger) : null; - this.configService = new ConfigService(sdkKey, monitor, options.pollingMode, options.cache, logger, fetcher, options.hooks, options.offline); + try { + StateMonitor monitor = options.context != null ? new AppStateMonitor(options.context, logger) : null; + this.configService = new ConfigService(sdkKey, monitor, options.pollingMode, options.cache, logger, fetcher, options.hooks, options.offline); + } catch (Exception e) { + fetcher.close(); + this.logger.error(0, "ConfigCatClient initialization failed.", e); + throw e; + } } else { this.hooks.invokeOnClientReady(ClientCacheState.HAS_LOCAL_OVERRIDE_FLAG_DATA_ONLY); } @@ -625,7 +631,11 @@ public static ConfigCatClient get(String sdkKey, Consumer optionsCallba return client; } - client = new ConfigCatClient(sdkKey, clientOptions); + try { + client = new ConfigCatClient(sdkKey, clientOptions); + } catch (IOException e) { + throw new RuntimeException("ConfigCatClient initialization failed.", e); + } INSTANCES.put(sdkKey, client); return client; } From 898a3927d669fa9c05344947aed649637d6c082d Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Sat, 30 May 2026 22:35:51 +0200 Subject: [PATCH 5/7] Fix sonarQube quality errors --- gradle/libs.versions.toml | 2 +- .../java/com/configcat/ConfigCatClient.java | 28 +++++----- .../com/configcat/ConfigCatClientTest.java | 55 +++++++++++++++++++ 3 files changed, 71 insertions(+), 14 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff948bc..db52bbe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.re logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" } mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "mockwebserver" } -mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockito-core = { module = "org.mockito:mockito-inline", version.ref = "mockito" } junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit-jupiter" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" } android-retrofuture = { module = "net.sourceforge.streamsupport:android-retrofuture", version.ref = "android-retrofuture" } diff --git a/src/main/java/com/configcat/ConfigCatClient.java b/src/main/java/com/configcat/ConfigCatClient.java index 5dadc79..49c79d0 100644 --- a/src/main/java/com/configcat/ConfigCatClient.java +++ b/src/main/java/com/configcat/ConfigCatClient.java @@ -41,22 +41,24 @@ private ConfigCatClient(String sdkKey, Options options) throws IllegalArgumentEx this.rolloutEvaluator = new RolloutEvaluator(this.logger); if (this.overrideBehaviour != OverrideBehaviour.LOCAL_ONLY) { - ConfigFetcher fetcher = new ConfigFetcher(options.httpOptions, - this.logger, - sdkKey, - !options.isBaseURLCustom() - ? options.dataGovernance == DataGovernance.GLOBAL - ? BASE_URL_GLOBAL - : BASE_URL_EU - : options.baseUrl, - options.isBaseURLCustom(), - options.pollingMode.getPollingIdentifier()); - + ConfigFetcher fetcher = null; + StateMonitor monitor = null; try { - StateMonitor monitor = options.context != null ? new AppStateMonitor(options.context, logger) : null; + fetcher = new ConfigFetcher(options.httpOptions, + this.logger, + sdkKey, + !options.isBaseURLCustom() + ? options.dataGovernance == DataGovernance.GLOBAL + ? BASE_URL_GLOBAL + : BASE_URL_EU + : options.baseUrl, + options.isBaseURLCustom(), + options.pollingMode.getPollingIdentifier()); + monitor = options.context != null ? new AppStateMonitor(options.context, logger) : null; this.configService = new ConfigService(sdkKey, monitor, options.pollingMode, options.cache, logger, fetcher, options.hooks, options.offline); } catch (Exception e) { - fetcher.close(); + if(fetcher != null) fetcher.close(); + if(monitor != null) monitor.close(); this.logger.error(0, "ConfigCatClient initialization failed.", e); throw e; } diff --git a/src/test/java/com/configcat/ConfigCatClientTest.java b/src/test/java/com/configcat/ConfigCatClientTest.java index abd2426..aa8d54d 100644 --- a/src/test/java/com/configcat/ConfigCatClientTest.java +++ b/src/test/java/com/configcat/ConfigCatClientTest.java @@ -8,6 +8,9 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import android.content.Context; +import org.mockito.MockedConstruction; + import java.io.File; import java.io.IOException; import java.lang.reflect.Field; @@ -19,6 +22,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; class ConfigCatClientTest { @@ -1015,4 +1019,55 @@ void testWaitForReady() throws IOException, InterruptedException, ExecutionExcep server.shutdown(); cl.close(); } + + @Test + void fetcherClosedWhenAppStateMonitorInitFails() throws IOException { + Context mockContext = mock(Context.class); + when(mockContext.getApplicationContext()).thenThrow(new RuntimeException("AppStateMonitor init failure")); + + try (MockedConstruction fetcherConstruction = mockConstruction(ConfigFetcher.class)) { + RuntimeException runtimeException = assertThrows(RuntimeException.class, () -> ConfigCatClient.get(Helpers.SDK_KEY, options -> { + options.pollingMode(PollingModes.manualPoll()); + options.watchAppStateChanges(mockContext); + })); + assertEquals("AppStateMonitor init failure", runtimeException.getMessage()); + + assertEquals(1, fetcherConstruction.constructed().size()); + ConfigFetcher constructedFetcher = fetcherConstruction.constructed().get(0); + verify(constructedFetcher).close(); + } + + ConfigCatClient.closeAll(); + } + + @Test + void fetcherAndMonitorClosedWhenConfigServiceInitFails() throws IOException { + Context mockContext = mock(Context.class); + + try (MockedConstruction fetcherConstruction = mockConstruction(ConfigFetcher.class); + MockedConstruction monitorConstruction = mockConstruction(AppStateMonitor.class)) { + + RuntimeException runtimeException = assertThrows(RuntimeException.class, () -> ConfigCatClient.get(Helpers.SDK_KEY, options -> { + options.pollingMode(PollingModes.manualPoll()); + options.watchAppStateChanges(mockContext); + // Adding a hook listener that throws causes ConfigService constructor to fail + // during setInitialized() -> hooks.invokeOnClientReady() + options.hooks().addOnClientReady(state -> { + throw new RuntimeException("ConfigService init failure"); + }); + })); + assertEquals("ConfigService init failure", runtimeException.getMessage()); + + assertEquals(1, fetcherConstruction.constructed().size()); + ConfigFetcher constructedFetcher = fetcherConstruction.constructed().get(0); + verify(constructedFetcher).close(); + + assertEquals(1, monitorConstruction.constructed().size()); + AppStateMonitor constructedMonitor = monitorConstruction.constructed().get(0); + verify(constructedMonitor).close(); + } + + ConfigCatClient.closeAll(); + } + } \ No newline at end of file From ebbacab034e4dc2be97c8cf068f688c13d436d0b Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Sun, 31 May 2026 12:48:14 +0200 Subject: [PATCH 6/7] Add extra test, fix inline name --- build.gradle | 2 +- gradle/libs.versions.toml | 2 +- .../java/com/configcat/ConfigFetcherTest.java | 62 ++++++++++++++++++- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 2ff1627..b421298 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,7 @@ dependencies { testImplementation(libs.logback.classic) testImplementation(libs.logback.core) testImplementation(libs.mockwebserver) - testImplementation(libs.mockito.core) + testImplementation(libs.mockito.inline) testRuntimeOnly(libs.junit.jupiter.engine) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db52bbe..fdaa52d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.re logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" } mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "mockwebserver" } -mockito-core = { module = "org.mockito:mockito-inline", version.ref = "mockito" } +mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito" } junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit-jupiter" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" } android-retrofuture = { module = "net.sourceforge.streamsupport:android-retrofuture", version.ref = "android-retrofuture" } diff --git a/src/test/java/com/configcat/ConfigFetcherTest.java b/src/test/java/com/configcat/ConfigFetcherTest.java index ce405e9..c8cc04f 100644 --- a/src/test/java/com/configcat/ConfigFetcherTest.java +++ b/src/test/java/com/configcat/ConfigFetcherTest.java @@ -4,6 +4,7 @@ import com.google.gson.GsonBuilder; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.SocketPolicy; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,8 +20,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; class ConfigFetcherTest { @@ -258,6 +258,64 @@ void fetchedReceivedInvalidBodyContainsCFRAY() throws Exception { fetcher.close(); } + @Test + void fetchFailedDueToRequestTimeoutContainsCFRAY() throws Exception { + this.server.enqueue(new MockResponse() + .setResponseCode(200) + .setHeader("CF-RAY", "timeout-ray-123") + .setBody(TEST_JSON) + .setBodyDelay(2, TimeUnit.SECONDS)); + + Logger mockLogger = mock(Logger.class); + ConfigCatLogger localLogger = new ConfigCatLogger(mockLogger, LogLevel.DEBUG, null, null); + + ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions().readTimeoutMillis(1000), + localLogger, + "", + this.server.url("/").toString(), + false, + PollingModes.manualPoll().getPollingIdentifier()); + + FetchResponse response = fetcher.fetchAsync(null).get(); + assertTrue(response.isFailed()); + assertTrue(response.error().toString().contains("Request timed out while trying to fetch config JSON.")); + assertTrue(response.error().toString().contains("(Ray ID: timeout-ray-123)")); + + verify(mockLogger, times(1)).error(anyString(), eq(1102), eq(ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(10000, 1000, "timeout-ray-123")), any(), any(Exception.class)); + + fetcher.close(); + } + + @Test + void fetchFailedDueToUnexpectedErrorContainsCFRAY() throws Exception { + // Trigger a non-timeout exception after cfRayId is read. + // Using chunked body with disconnect causes IOException during body read or invalid body parse, + // both paths propagate cfRayId from the response headers. + okio.Buffer body = new okio.Buffer().writeUtf8(TEST_JSON); + this.server.enqueue(new MockResponse() + .setResponseCode(200) + .setHeader("CF-RAY", "unexpected-ray-456") + .setChunkedBody(body, 5) + .setSocketPolicy(SocketPolicy.DISCONNECT_DURING_RESPONSE_BODY)); + + Logger mockLogger = mock(Logger.class); + ConfigCatLogger localLogger = new ConfigCatLogger(mockLogger, LogLevel.DEBUG, null, null); + + ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), + localLogger, + "", + this.server.url("/").toString(), + false, + PollingModes.manualPoll().getPollingIdentifier()); + + FetchResponse response = fetcher.fetchAsync(null).get(); + assertTrue(response.isFailed()); + assertTrue(response.error().toString().contains("Unexpected error occurred while trying to fetch config JSON.")); + assertTrue(response.error().toString().contains("(Ray ID: unexpected-ray-456)")); + + fetcher.close(); + } + @Test void ensureStateMonitorWorks() throws IOException { this.server.enqueue(new MockResponse().setResponseCode(200).setBody(TEST_JSON)); From 82a87bf663975e1bd8e830e405e8d7a046ba5354 Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Wed, 3 Jun 2026 13:40:14 +0200 Subject: [PATCH 7/7] Remove redundant error logging during ConfigCatClient initialization --- src/main/java/com/configcat/ConfigCatClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/configcat/ConfigCatClient.java b/src/main/java/com/configcat/ConfigCatClient.java index 49c79d0..089a273 100644 --- a/src/main/java/com/configcat/ConfigCatClient.java +++ b/src/main/java/com/configcat/ConfigCatClient.java @@ -59,7 +59,6 @@ private ConfigCatClient(String sdkKey, Options options) throws IllegalArgumentEx } catch (Exception e) { if(fetcher != null) fetcher.close(); if(monitor != null) monitor.close(); - this.logger.error(0, "ConfigCatClient initialization failed.", e); throw e; } } else {