From b2ed916e7d50079bb3c98e0e970760f91f57f2af Mon Sep 17 00:00:00 2001 From: Aitor Viana Date: Fri, 19 Dec 2025 14:24:19 +0000 Subject: [PATCH 1/3] Scaffold duck.ai ATB retention API --- .../browser/WebViewRequestInterceptorTest.kt | 2 + .../app/statistics/api/AtbLifecyclePlugin.kt | 7 +++ .../app/statistics/api/StatisticsUpdater.kt | 22 +++++++++ .../statistics/store/StatisticsDataStore.kt | 1 + .../app/statistics/AtbInitializer.kt | 4 +- .../app/statistics/api/StatisticsRequester.kt | 48 +++++++++++++++---- .../app/statistics/api/StatisticsService.kt | 10 +++- .../store/StatisticsSharedPreferences.kt | 6 +++ .../user_segments/SegmentCalculationTest.kt | 2 + 9 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/api/StatisticsUpdater.kt diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/WebViewRequestInterceptorTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/WebViewRequestInterceptorTest.kt index 845f161fef40..800a57b45594 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/WebViewRequestInterceptorTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/WebViewRequestInterceptorTest.kt @@ -910,6 +910,8 @@ class WebViewRequestInterceptorTest { override var searchRetentionAtb: String? = "" + override var duckaiRetentionAtb: String? = "" + override var variant: String? = "" override var referrerVariant: String? = "" diff --git a/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/api/AtbLifecyclePlugin.kt b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/api/AtbLifecyclePlugin.kt index 0726bd5f17fb..057eb054dfdc 100644 --- a/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/api/AtbLifecyclePlugin.kt +++ b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/api/AtbLifecyclePlugin.kt @@ -31,6 +31,13 @@ interface AtbLifecyclePlugin { // default is no-op } + /** + * Will be called right after we have refreshed the ATB retention on duck.ai + */ + fun onDuckAiRetentionAtbRefreshed(oldAtb: String, newAtb: String) { + // default is no-op + } + /** * Will be called right after the ATB is first initialized and successfully sent via exti call */ diff --git a/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/api/StatisticsUpdater.kt b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/api/StatisticsUpdater.kt new file mode 100644 index 000000000000..bb7a17e0c718 --- /dev/null +++ b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/api/StatisticsUpdater.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.statistics.api + +interface StatisticsUpdater { + fun refreshSearchRetentionAtb() + fun refreshDuckAiRetentionAtb() +} diff --git a/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/store/StatisticsDataStore.kt b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/store/StatisticsDataStore.kt index 1a854af5db79..64f1bcc89159 100644 --- a/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/store/StatisticsDataStore.kt +++ b/statistics/statistics-api/src/main/java/com/duckduckgo/app/statistics/store/StatisticsDataStore.kt @@ -28,6 +28,7 @@ interface StatisticsDataStore { var atb: Atb? var appRetentionAtb: String? var searchRetentionAtb: String? + var duckaiRetentionAtb: String? var variant: String? var referrerVariant: String? diff --git a/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/AtbInitializer.kt b/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/AtbInitializer.kt index 2056871df23f..22bf10703990 100644 --- a/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/AtbInitializer.kt +++ b/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/AtbInitializer.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.LifecycleOwner import com.duckduckgo.anvil.annotations.ContributesPluginPoint import com.duckduckgo.app.di.AppCoroutineScope import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver -import com.duckduckgo.app.statistics.api.StatisticsUpdater +import com.duckduckgo.app.statistics.api.StatisticsRequester import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.app.statistics.pixels.StatisticsPixelName.ATB_PRE_INITIALIZER_PLUGIN_TIMEOUT import com.duckduckgo.app.statistics.store.StatisticsDataStore @@ -50,7 +50,7 @@ import javax.inject.Inject class AtbInitializer @Inject constructor( @AppCoroutineScope private val appCoroutineScope: CoroutineScope, private val statisticsDataStore: StatisticsDataStore, - private val statisticsUpdater: StatisticsUpdater, + private val statisticsUpdater: StatisticsRequester, private val listeners: PluginPoint, private val dispatcherProvider: DispatcherProvider, private val pixel: Pixel, diff --git a/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/api/StatisticsRequester.kt b/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/api/StatisticsRequester.kt index e623fbdf2250..0b6d21c5ced2 100644 --- a/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/api/StatisticsRequester.kt +++ b/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/api/StatisticsRequester.kt @@ -26,6 +26,7 @@ import com.duckduckgo.common.utils.plugins.PluginPoint import com.duckduckgo.di.scopes.AppScope import com.duckduckgo.experiments.api.VariantManager import com.squareup.anvil.annotations.ContributesBinding +import dagger.SingleInstanceIn import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -35,13 +36,11 @@ import logcat.LogPriority.WARN import logcat.logcat import javax.inject.Inject -interface StatisticsUpdater { - fun initializeAtb() - fun refreshSearchRetentionAtb() - fun refreshAppRetentionAtb() -} - -@ContributesBinding(AppScope::class) +@ContributesBinding( + scope = AppScope::class, + boundType = StatisticsUpdater::class, +) +@SingleInstanceIn(AppScope::class) class StatisticsRequester @Inject constructor( private val store: StatisticsDataStore, private val service: StatisticsService, @@ -57,7 +56,7 @@ class StatisticsRequester @Inject constructor( * consume referer data */ @SuppressLint("CheckResult") - override fun initializeAtb() { + fun initializeAtb() { logcat(INFO) { "Initializing ATB" } if (store.hasInstallationStatistics) { @@ -135,8 +134,39 @@ class StatisticsRequester @Inject constructor( } } + override fun refreshDuckAiRetentionAtb() { + val atb = store.atb + + if (atb == null) { + initializeAtb() + return + } + + appCoroutineScope.launch(dispatchers.io()) { + val fullAtb = atb.formatWithVariant(variantManager.getVariantKey()) + val oldDuckAiAtb = store.duckaiRetentionAtb ?: atb.version + + service + .updateDuckAiAtb( + atb = fullAtb, + retentionAtb = oldDuckAiAtb, + email = emailSignInState(), + ) + .subscribeOn(Schedulers.io()) + .subscribe( + { + logcat(VERBOSE) { "Duck.ai atb refresh succeeded, latest atb is ${it.version}" } + store.duckaiRetentionAtb = it.version + storeUpdateVersionIfPresent(it) + plugins.getPlugins().forEach { plugin -> plugin.onDuckAiRetentionAtbRefreshed(oldDuckAiAtb, it.version) } + }, + { logcat(VERBOSE) { "Duck.ai atb refresh failed with error ${it.localizedMessage}" } }, + ) + } + } + @SuppressLint("CheckResult") - override fun refreshAppRetentionAtb() { + fun refreshAppRetentionAtb() { val atb = store.atb if (atb == null) { diff --git a/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/api/StatisticsService.kt b/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/api/StatisticsService.kt index af3942f8b146..6af55fe0c7fd 100644 --- a/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/api/StatisticsService.kt +++ b/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/api/StatisticsService.kt @@ -41,7 +41,6 @@ interface StatisticsService { @Query(ParamKey.EMAIL) email: Int?, ): Observable - // ANA @GET("/atb.js") fun updateSearchAtb( @Query(ParamKey.ATB) atb: String, @@ -50,7 +49,6 @@ interface StatisticsService { @Query(ParamKey.EMAIL) email: Int?, ): Observable - // ANA @GET("/atb.js?at=app_use") fun updateAppAtb( @Query(ParamKey.ATB) atb: String, @@ -58,4 +56,12 @@ interface StatisticsService { @Query(ParamKey.DEV_MODE) devMode: Int? = if (BuildConfig.DEBUG) 1 else null, @Query(ParamKey.EMAIL) email: Int?, ): Observable + + @GET("/atb.js?at=duckai") + fun updateDuckAiAtb( + @Query(ParamKey.ATB) atb: String, + @Query(ParamKey.RETENTION_ATB) retentionAtb: String, + @Query(ParamKey.DEV_MODE) devMode: Int? = if (BuildConfig.DEBUG) 1 else null, + @Query(ParamKey.EMAIL) email: Int?, + ): Observable } diff --git a/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/store/StatisticsSharedPreferences.kt b/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/store/StatisticsSharedPreferences.kt index e11c292d2d32..e37e6fea12f9 100644 --- a/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/store/StatisticsSharedPreferences.kt +++ b/statistics/statistics-impl/src/main/java/com/duckduckgo/app/statistics/store/StatisticsSharedPreferences.kt @@ -47,6 +47,10 @@ class StatisticsSharedPreferences @Inject constructor(private val context: Conte get() = preferences.getString(KEY_SEARCH_RETENTION_ATB, null) set(value) = preferences.edit { putString(KEY_SEARCH_RETENTION_ATB, value) } + override var duckaiRetentionAtb: String? + get() = preferences.getString(KEY_DUCK_AI_RETENTION_ATB, null) + set(value) = preferences.edit { putString(KEY_DUCK_AI_RETENTION_ATB, value) } + override var appRetentionAtb: String? get() = preferences.getString(KEY_APP_RETENTION_ATB, null) set(value) = preferences.edit { putString(KEY_APP_RETENTION_ATB, value) } @@ -65,6 +69,8 @@ class StatisticsSharedPreferences @Inject constructor(private val context: Conte private const val FILENAME = "com.duckduckgo.app.statistics" private const val KEY_ATB = "com.duckduckgo.app.statistics.atb" private const val KEY_SEARCH_RETENTION_ATB = "com.duckduckgo.app.statistics.retentionatb" + + private const val KEY_DUCK_AI_RETENTION_ATB = "com.duckduckgo.app.statistics.duckairetentionatb" private const val KEY_APP_RETENTION_ATB = "com.duckduckgo.app.statistics.appretentionatb" private const val KEY_VARIANT = "com.duckduckgo.app.statistics.variant" private const val KEY_REFERRER_VARIANT = "com.duckduckgo.app.statistics.referrerVariant" diff --git a/statistics/statistics-impl/src/test/java/com/duckduckgo/app/statistics/user_segments/SegmentCalculationTest.kt b/statistics/statistics-impl/src/test/java/com/duckduckgo/app/statistics/user_segments/SegmentCalculationTest.kt index bd6bac20db44..f475cd57fed9 100644 --- a/statistics/statistics-impl/src/test/java/com/duckduckgo/app/statistics/user_segments/SegmentCalculationTest.kt +++ b/statistics/statistics-impl/src/test/java/com/duckduckgo/app/statistics/user_segments/SegmentCalculationTest.kt @@ -148,6 +148,8 @@ private class FakeStatisticsDataStore : StatisticsDataStore { override var searchRetentionAtb: String? = "" + override var duckaiRetentionAtb: String? = "" + override var variant: String? = "" override var referrerVariant: String? = "" From f10be3abc3c49afac3527a509dff3ea42e9fad7c Mon Sep 17 00:00:00 2001 From: Aitor Viana Date: Fri, 19 Dec 2025 14:51:35 +0000 Subject: [PATCH 2/3] Refresh the duck.ai ATB when prompt is submitted --- .../duckchat/impl/pixel/DuckChatPixels.kt | 16 ++++++++++++++-- .../impl/pixel/RealDuckChatPixelsTest.kt | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/pixel/DuckChatPixels.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/pixel/DuckChatPixels.kt index 844fd0d1a71b..b7374f0a4d34 100644 --- a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/pixel/DuckChatPixels.kt +++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/pixel/DuckChatPixels.kt @@ -17,6 +17,7 @@ package com.duckduckgo.duckchat.impl.pixel import com.duckduckgo.app.di.AppCoroutineScope +import com.duckduckgo.app.statistics.api.StatisticsUpdater import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin @@ -120,16 +121,24 @@ class RealDuckChatPixels @Inject constructor( private val duckChatFeatureRepository: DuckChatFeatureRepository, @AppCoroutineScope private val appCoroutineScope: CoroutineScope, private val dispatcherProvider: DispatcherProvider, + private val statisticsUpdater: StatisticsUpdater, ) : DuckChatPixels { override fun sendReportMetricPixel(reportMetric: ReportMetric) { appCoroutineScope.launch(dispatcherProvider.io()) { + var refreshAtb = false val sessionParams = mapOf( DuckChatPixelParameters.DELTA_TIMESTAMP_PARAMETERS to duckChatFeatureRepository.sessionDeltaInMinutes().toString(), ) val (pixelName, params) = when (reportMetric) { - USER_DID_SUBMIT_PROMPT -> DUCK_CHAT_SEND_PROMPT_ONGOING_CHAT to sessionParams - USER_DID_SUBMIT_FIRST_PROMPT -> DUCK_CHAT_START_NEW_CONVERSATION to sessionParams + USER_DID_SUBMIT_PROMPT -> { + refreshAtb = true + DUCK_CHAT_SEND_PROMPT_ONGOING_CHAT to sessionParams + } + USER_DID_SUBMIT_FIRST_PROMPT -> { + refreshAtb = true + DUCK_CHAT_START_NEW_CONVERSATION to sessionParams + } USER_DID_OPEN_HISTORY -> DUCK_CHAT_OPEN_HISTORY to sessionParams USER_DID_SELECT_FIRST_HISTORY_ITEM -> DUCK_CHAT_OPEN_MOST_RECENT_HISTORY_CHAT to sessionParams USER_DID_CREATE_NEW_CHAT -> DUCK_CHAT_START_NEW_CONVERSATION_BUTTON_CLICKED to sessionParams @@ -138,6 +147,9 @@ class RealDuckChatPixels @Inject constructor( withContext(dispatcherProvider.main()) { pixel.fire(pixelName, parameters = params) + if (refreshAtb) { + statisticsUpdater.refreshDuckAiRetentionAtb() + } } } } diff --git a/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/pixel/RealDuckChatPixelsTest.kt b/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/pixel/RealDuckChatPixelsTest.kt index 278f87268471..cb44656fef4c 100644 --- a/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/pixel/RealDuckChatPixelsTest.kt +++ b/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/pixel/RealDuckChatPixelsTest.kt @@ -16,6 +16,7 @@ package com.duckduckgo.duckchat.impl.pixel +import com.duckduckgo.app.statistics.api.StatisticsUpdater import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.common.test.CoroutineTestRule import com.duckduckgo.duckchat.impl.ReportMetric.USER_DID_CREATE_NEW_CHAT @@ -39,6 +40,7 @@ import org.junit.Rule import org.junit.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @@ -50,6 +52,8 @@ class RealDuckChatPixelsTest { private val mockPixel: Pixel = mock() private val mockDuckChatFeatureRepository: DuckChatFeatureRepository = mock() + private val statisticsUpdater: StatisticsUpdater = mock() + private lateinit var testee: RealDuckChatPixels @Before @@ -61,6 +65,7 @@ class RealDuckChatPixelsTest { duckChatFeatureRepository = mockDuckChatFeatureRepository, appCoroutineScope = coroutineRule.testScope, dispatcherProvider = coroutineRule.testDispatcherProvider, + statisticsUpdater = statisticsUpdater, ) } @@ -76,6 +81,7 @@ class RealDuckChatPixelsTest { DUCK_CHAT_SEND_PROMPT_ONGOING_CHAT, parameters = mapOf(DuckChatPixelParameters.DELTA_TIMESTAMP_PARAMETERS to "5"), ) + verify(statisticsUpdater).refreshDuckAiRetentionAtb() } @Test @@ -90,6 +96,7 @@ class RealDuckChatPixelsTest { DUCK_CHAT_START_NEW_CONVERSATION, parameters = mapOf(DuckChatPixelParameters.DELTA_TIMESTAMP_PARAMETERS to "10"), ) + verify(statisticsUpdater).refreshDuckAiRetentionAtb() } @Test @@ -104,6 +111,7 @@ class RealDuckChatPixelsTest { DUCK_CHAT_OPEN_HISTORY, parameters = mapOf(DuckChatPixelParameters.DELTA_TIMESTAMP_PARAMETERS to "15"), ) + verifyNoInteractions(statisticsUpdater) } @Test @@ -118,6 +126,7 @@ class RealDuckChatPixelsTest { DUCK_CHAT_OPEN_MOST_RECENT_HISTORY_CHAT, parameters = mapOf(DuckChatPixelParameters.DELTA_TIMESTAMP_PARAMETERS to "20"), ) + verifyNoInteractions(statisticsUpdater) } @Test @@ -132,6 +141,7 @@ class RealDuckChatPixelsTest { DUCK_CHAT_START_NEW_CONVERSATION_BUTTON_CLICKED, parameters = mapOf(DuckChatPixelParameters.DELTA_TIMESTAMP_PARAMETERS to "25"), ) + verifyNoInteractions(statisticsUpdater) } @Test @@ -141,5 +151,6 @@ class RealDuckChatPixelsTest { advanceUntilIdle() verify(mockPixel).fire(DUCK_CHAT_KEYBOARD_RETURN_PRESSED, parameters = emptyMap()) + verifyNoInteractions(statisticsUpdater) } } From 86ce874c93eaa13d22fbcf935c588ad555ae42de Mon Sep 17 00:00:00 2001 From: Aitor Viana Date: Fri, 19 Dec 2025 15:42:01 +0000 Subject: [PATCH 3/3] Add android tests --- .../api/StatisticsRequesterJsonTest.kt | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/app/src/androidTest/java/com/duckduckgo/app/statistics/api/StatisticsRequesterJsonTest.kt b/app/src/androidTest/java/com/duckduckgo/app/statistics/api/StatisticsRequesterJsonTest.kt index 1192bc29e42b..f1d0ef145adb 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/statistics/api/StatisticsRequesterJsonTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/statistics/api/StatisticsRequesterJsonTest.kt @@ -112,6 +112,14 @@ class StatisticsRequesterJsonTest { assertEquals("v99-1", statisticsStore.atb?.version) } + @Test + fun whenAlreadyInitializedRefreshDuckAiRetentionCallWithUpdateVersionResponseUpdatesAtb() { + statisticsStore.saveAtb(Atb("100-1")) + queueResponseFromFile(VALID_UPDATE_RESPONSE_JSON) + testee.refreshDuckAiRetentionAtb() + assertEquals("v99-1", statisticsStore.atb?.version) + } + @Test fun whenNotYetInitializedAtbInitializationStoresAtbResponse() { queueResponseFromFile(VALID_JSON) @@ -235,6 +243,16 @@ class StatisticsRequesterJsonTest { assertEquals("app_use", refreshRequest?.extractQueryParam("at")) } + @Test + fun whenAlreadyInitializedRefreshDuckAiCallGoesToCorrectEndpoint() { + statisticsStore.saveAtb(Atb("100-1")) + queueResponseFromFile(VALID_REFRESH_RESPONSE_JSON) + testee.refreshDuckAiRetentionAtb() + val refreshRequest = takeRequestImmediately() + assertEquals("/atb.js", refreshRequest?.encodedPath()) + assertEquals("duckai", refreshRequest?.extractQueryParam("at")) + } + @Test fun whenAlreadyInitializedRefreshSearchCallUpdatesSearchRetentionAtb() { statisticsStore.saveAtb(Atb("100-1")) @@ -251,6 +269,14 @@ class StatisticsRequesterJsonTest { assertEquals("v107-7", statisticsStore.appRetentionAtb) } + @Test + fun whenAlreadyInitializedRefreshDuckAiCallUpdatesAppRetentionAtb() { + statisticsStore.saveAtb(Atb("100-1")) + queueResponseFromFile(VALID_REFRESH_RESPONSE_JSON) + testee.refreshDuckAiRetentionAtb() + assertEquals("v107-7", statisticsStore.appRetentionAtb) + } + @Test fun whenAlreadyInitializedRefreshSearchCallSendsTestParameter() { statisticsStore.saveAtb(Atb("100-1")) @@ -271,6 +297,16 @@ class StatisticsRequesterJsonTest { assertTestParameterSent(testParam) } + @Test + fun whenAlreadyInitializedRefreshDuckAiCallSendsTestParameter() { + statisticsStore.saveAtb(Atb("100-1")) + queueResponseFromFile(VALID_REFRESH_RESPONSE_JSON) + testee.refreshDuckAiRetentionAtb() + val refreshRequest = takeRequestImmediately() + val testParam = refreshRequest?.extractQueryParam(ParamKey.DEV_MODE) + assertTestParameterSent(testParam) + } + @Test fun whenAlreadyInitializedRefreshSearchCallSendsCorrectAtb() { statisticsStore.saveAtb(Atb("100-1")) @@ -291,6 +327,16 @@ class StatisticsRequesterJsonTest { assertEquals("100-1ma", atbParam) } + @Test + fun whenAlreadyInitializedRefreshDuckAiCallSendsCorrectAtb() { + statisticsStore.saveAtb(Atb("100-1")) + queueResponseFromFile(VALID_REFRESH_RESPONSE_JSON) + testee.refreshDuckAiRetentionAtb() + val refreshRequest = takeRequestImmediately() + val atbParam = refreshRequest?.extractQueryParam(ParamKey.ATB) + assertEquals("100-1ma", atbParam) + } + @Test fun whenAlreadyInitializedRefreshSearchCallSendsCorrectRetentionAtb() { statisticsStore.saveAtb(Atb("100-1")) @@ -313,6 +359,17 @@ class StatisticsRequesterJsonTest { assertEquals("101-3", atbParam) } + @Test + fun whenAlreadyInitializedRefreshDuckAiCallSendsCorrectRetentionAtb() { + statisticsStore.saveAtb(Atb("100-1")) + statisticsStore.appRetentionAtb = "101-3" + queueResponseFromFile(VALID_REFRESH_RESPONSE_JSON) + testee.refreshDuckAiRetentionAtb() + val refreshRequest = takeRequestImmediately() + val atbParam = refreshRequest?.extractQueryParam(ParamKey.RETENTION_ATB) + assertEquals("101-3", atbParam) + } + /** * Should there be an issue obtaining the request, this will avoid the tests stalling indefinitely. *