From 507b47f1c0bf04f121c7c1cbd988409fc80c5bc0 Mon Sep 17 00:00:00 2001 From: duyhungtnn Date: Wed, 22 Oct 2025 11:55:21 +0700 Subject: [PATCH 1/3] Add tests for polling and retry interval behavior Added tests to verify EvaluationForegroundTask scheduling logic when pollingInterval is less than, equal to, or greater than retryPollingInterval. Ensures correct request timing and retry behavior under various configurations and error conditions. --- .../scheduler/EvaluationForegroundTaskTest.kt | 152 +++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt b/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt index 59d2e6a0..c61070c9 100644 --- a/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt +++ b/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt @@ -95,6 +95,11 @@ class EvaluationForegroundTaskTest { assertThat(server.requestCount).isEqualTo(1) assertThat(time).isAtLeast(980L) + + // Should continue scheduling after success when retryCount is 0 + val (time2, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } + assertThat(server.requestCount).isEqualTo(2) + assertThat(time2).isAtLeast(980L) } @Test @@ -109,11 +114,18 @@ class EvaluationForegroundTaskTest { ) task.start() + assertThat(server.requestCount).isEqualTo(0) + + val (time, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } + + assertThat(server.requestCount).isEqualTo(1) + assertThat(time).isAtLeast(980L) + task.stop() val request = server.takeRequest(2, TimeUnit.SECONDS) assertThat(request).isNull() - assertThat(server.requestCount).isEqualTo(0) + assertThat(server.requestCount).isEqualTo(1) } @Test @@ -218,4 +230,142 @@ class EvaluationForegroundTaskTest { val (time3, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } assertThat(time3).isAtLeast(990L) } + + @Test + fun `retry - should not retry when pollingInterval is short enough`() { + // Set up a config with pollingInterval <= retryPollingInterval + val shortPollingConfig = createTestBKTConfig( + apiKey = "api_key_value", + apiEndpoint = server.url("").toString(), + featureTag = "feature_tag_value", + eventsMaxBatchQueueCount = 3, + pollingInterval = 500, // shorter than retry interval + appVersion = "1.2.3", + ) + + val shortPollingComponent = ComponentImpl( + dataModule = DataModule( + application = ApplicationProvider.getApplicationContext(), + config = shortPollingConfig, + user = user1, + inMemoryDB = true, + ), + interactorModule = InteractorModule( + mainHandler = Handler(Looper.getMainLooper()), + ), + ) + + task = EvaluationForegroundTask( + shortPollingComponent, + executor, + retryPollingInterval = 800, // longer than pollingInterval + maxRetryCount = 3, + ) + + // Enqueue error responses + server.enqueueResponse(moshi, 500, ErrorResponse(ErrorResponse.ErrorDetail(500, "500 error"))) + server.enqueueResponse(moshi, 500, ErrorResponse(ErrorResponse.ErrorDetail(500, "500 error"))) + server.enqueueResponse(moshi, 500, ErrorResponse(ErrorResponse.ErrorDetail(500, "500 error"))) + + task.start() + + // Note: adding 150ms buffer to account for execution/mock server delays + // 490L < x < pollingInterval + 150ms + // initial request + val (time1, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } + assertThat(time1).isAtLeast(490L) + assertThat(time1).isLessThan(690L) + assertThat(server.requestCount).isEqualTo(1) + + // subsequent requests should continue with normal polling interval, not retry interval + val (time2, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } + assertThat(time2).isAtLeast(490L) + assertThat(time2).isLessThan(600L) + assertThat(server.requestCount).isEqualTo(2) + + val (time3, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } + assertThat(time3).isAtLeast(490L) + assertThat(time3).isLessThan(600L) + assertThat(server.requestCount).isEqualTo(3) + + // Cleanup + executor.submit { + shortPollingComponent.dataModule.destroy() + } + } + + @Test + fun `should continue scheduling after error when pollingInterval equal retryInterval`(){ + // Set up a config with pollingInterval equal to retryPollingInterval + val equalIntervalConfig = createTestBKTConfig( + apiKey = "api_key_value", + apiEndpoint = server.url("").toString(), + featureTag = "feature_tag_value", + eventsMaxBatchQueueCount = 3, + pollingInterval = 800, // equal to retry interval + appVersion = "1.2.3", + ) + + val equalIntervalComponent = ComponentImpl( + dataModule = DataModule( + application = ApplicationProvider.getApplicationContext(), + config = equalIntervalConfig, + user = user1, + inMemoryDB = true, + ), + interactorModule = InteractorModule( + mainHandler = Handler(Looper.getMainLooper()), + ), + ) + + task = EvaluationForegroundTask( + equalIntervalComponent, + executor, + retryPollingInterval = 800, // equal to pollingInterval + maxRetryCount = 3, + ) + + // Enqueue multiple error responses followed by a success + server.enqueueResponse(moshi, 500, ErrorResponse(ErrorResponse.ErrorDetail(500, "500 error"))) + server.enqueueResponse(moshi, 500, ErrorResponse(ErrorResponse.ErrorDetail(500, "500 error"))) + server.enqueueResponse(moshi, 500, ErrorResponse(ErrorResponse.ErrorDetail(500, "500 error"))) + server.enqueueResponse( + moshi, + 200, + GetEvaluationsResponse( + evaluations = user1Evaluations, + userEvaluationsId = "user_evaluations_id_value", + ), + ) + + task.start() + + // All requests should continue with the same interval (790ms < x < retryPollingInterval + 150ms ) despite errors + // Note: adding 150ms buffer to account for execution/mock server delays + val (time1, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } + assertThat(time1).isAtLeast(790L) + assertThat(time1).isLessThan(950L) + assertThat(server.requestCount).isEqualTo(1) + + val (time2, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } + assertThat(time2).isAtLeast(790L) + assertThat(time2).isLessThan(950L) + assertThat(server.requestCount).isEqualTo(2) + + val (time3, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } + assertThat(time3).isAtLeast(790L) + assertThat(time3).isLessThan(950L) + assertThat(server.requestCount).isEqualTo(3) + + // Success request should also continue with same interval + val (time4, _) = measureTimeMillisWithResult { server.takeRequest(2, TimeUnit.SECONDS) } + assertThat(time4).isAtLeast(790L) + assertThat(time4).isLessThan(950L) + assertThat(server.requestCount).isEqualTo(4) + + // Cleanup + executor.submit { + equalIntervalComponent.dataModule.destroy() + } + } } From f066450e9541714c444e81cade8a85219a39e31a Mon Sep 17 00:00:00 2001 From: duyhungtnn Date: Wed, 22 Oct 2025 12:04:41 +0700 Subject: [PATCH 2/3] fix: formatting in test function declaration Corrected spacing in the function signature of a test to improve code readability and maintain consistency with Kotlin style guidelines. --- .../android/internal/scheduler/EvaluationForegroundTaskTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt b/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt index c61070c9..ec43ae47 100644 --- a/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt +++ b/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt @@ -295,7 +295,7 @@ class EvaluationForegroundTaskTest { } @Test - fun `should continue scheduling after error when pollingInterval equal retryInterval`(){ + fun `should continue scheduling after error when pollingInterval equal retryInterval`() { // Set up a config with pollingInterval equal to retryPollingInterval val equalIntervalConfig = createTestBKTConfig( apiKey = "api_key_value", From 0e0a772d6e031f80966ddcf92c2143957ffab6eb Mon Sep 17 00:00:00 2001 From: duyhungtnn Date: Wed, 22 Oct 2025 12:05:51 +0700 Subject: [PATCH 3/3] fix: lint fail Improves readability in EvaluationForegroundTaskTest by reformatting the initialization of test configs and components to use consistent indentation and line breaks. No functional changes were made. --- .../scheduler/EvaluationForegroundTaskTest.kt | 110 ++++++++++-------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt b/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt index ec43ae47..a369e12c 100644 --- a/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt +++ b/bucketeer/src/test/kotlin/io/bucketeer/sdk/android/internal/scheduler/EvaluationForegroundTaskTest.kt @@ -234,33 +234,38 @@ class EvaluationForegroundTaskTest { @Test fun `retry - should not retry when pollingInterval is short enough`() { // Set up a config with pollingInterval <= retryPollingInterval - val shortPollingConfig = createTestBKTConfig( - apiKey = "api_key_value", - apiEndpoint = server.url("").toString(), - featureTag = "feature_tag_value", - eventsMaxBatchQueueCount = 3, - pollingInterval = 500, // shorter than retry interval - appVersion = "1.2.3", - ) + val shortPollingConfig = + createTestBKTConfig( + apiKey = "api_key_value", + apiEndpoint = server.url("").toString(), + featureTag = "feature_tag_value", + eventsMaxBatchQueueCount = 3, + pollingInterval = 500, // shorter than retry interval + appVersion = "1.2.3", + ) - val shortPollingComponent = ComponentImpl( - dataModule = DataModule( - application = ApplicationProvider.getApplicationContext(), - config = shortPollingConfig, - user = user1, - inMemoryDB = true, - ), - interactorModule = InteractorModule( - mainHandler = Handler(Looper.getMainLooper()), - ), - ) + val shortPollingComponent = + ComponentImpl( + dataModule = + DataModule( + application = ApplicationProvider.getApplicationContext(), + config = shortPollingConfig, + user = user1, + inMemoryDB = true, + ), + interactorModule = + InteractorModule( + mainHandler = Handler(Looper.getMainLooper()), + ), + ) - task = EvaluationForegroundTask( - shortPollingComponent, - executor, - retryPollingInterval = 800, // longer than pollingInterval - maxRetryCount = 3, - ) + task = + EvaluationForegroundTask( + shortPollingComponent, + executor, + retryPollingInterval = 800, // longer than pollingInterval + maxRetryCount = 3, + ) // Enqueue error responses server.enqueueResponse(moshi, 500, ErrorResponse(ErrorResponse.ErrorDetail(500, "500 error"))) @@ -297,33 +302,38 @@ class EvaluationForegroundTaskTest { @Test fun `should continue scheduling after error when pollingInterval equal retryInterval`() { // Set up a config with pollingInterval equal to retryPollingInterval - val equalIntervalConfig = createTestBKTConfig( - apiKey = "api_key_value", - apiEndpoint = server.url("").toString(), - featureTag = "feature_tag_value", - eventsMaxBatchQueueCount = 3, - pollingInterval = 800, // equal to retry interval - appVersion = "1.2.3", - ) + val equalIntervalConfig = + createTestBKTConfig( + apiKey = "api_key_value", + apiEndpoint = server.url("").toString(), + featureTag = "feature_tag_value", + eventsMaxBatchQueueCount = 3, + pollingInterval = 800, // equal to retry interval + appVersion = "1.2.3", + ) - val equalIntervalComponent = ComponentImpl( - dataModule = DataModule( - application = ApplicationProvider.getApplicationContext(), - config = equalIntervalConfig, - user = user1, - inMemoryDB = true, - ), - interactorModule = InteractorModule( - mainHandler = Handler(Looper.getMainLooper()), - ), - ) + val equalIntervalComponent = + ComponentImpl( + dataModule = + DataModule( + application = ApplicationProvider.getApplicationContext(), + config = equalIntervalConfig, + user = user1, + inMemoryDB = true, + ), + interactorModule = + InteractorModule( + mainHandler = Handler(Looper.getMainLooper()), + ), + ) - task = EvaluationForegroundTask( - equalIntervalComponent, - executor, - retryPollingInterval = 800, // equal to pollingInterval - maxRetryCount = 3, - ) + task = + EvaluationForegroundTask( + equalIntervalComponent, + executor, + retryPollingInterval = 800, // equal to pollingInterval + maxRetryCount = 3, + ) // Enqueue multiple error responses followed by a success server.enqueueResponse(moshi, 500, ErrorResponse(ErrorResponse.ErrorDetail(500, "500 error")))