Bug 1868120 - RC recommendation impression once per product aid combination

(cherry picked from commit 13b68e6d771d052767e856644cf50d44282eb350)
fenix/122.0
rahulsainani 5 months ago committed by mergify[bot]
parent 8257ce33ee
commit 2871b60a2f

@ -15,6 +15,7 @@ import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory
import org.mozilla.fenix.browser.StandardSnackbarError
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.shopping.ShoppingState
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesSelectedCategory
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
@ -250,5 +251,12 @@ sealed class AppAction : Action {
val productPageUrl: String,
val expanded: Boolean,
) : ShoppingAction()
/**
* [ShoppingAction] used to update the recorded product recommendation impressions set.
*/
data class ProductRecommendationImpression(
val key: ShoppingState.ProductRecommendationImpressionKey,
) : ShoppingAction()
}
}

@ -10,12 +10,28 @@ package org.mozilla.fenix.components.appstate.shopping
* @property shoppingSheetExpanded Boolean indicating if the shopping sheet is expanded and visible.
* @property productCardState Map of product url to [CardState] that contains the state of different
* cards in the shopping sheet.
* @property recordedProductRecommendationImpressions Set of [ProductRecommendationImpressionKey]
* that contains the product recommendation impressions that have been recorded.
*/
data class ShoppingState(
val shoppingSheetExpanded: Boolean? = null,
val productCardState: Map<String, CardState> = emptyMap(),
val recordedProductRecommendationImpressions: Set<ProductRecommendationImpressionKey> = emptySet(),
) {
/**
* Key for a product recommendation impression.
*
* @property tabId The id of the tab that the product and recommendation is displayed in.
* @property productUrl The url of the product.
* @property aid The id of the recommendation.
*/
data class ProductRecommendationImpressionKey(
val tabId: String,
val productUrl: String,
val aid: String,
)
/**
* State for different cards in the shopping sheet for a product.
*

@ -64,6 +64,13 @@ internal object ShoppingStateReducer {
),
)
}
is ShoppingAction.ProductRecommendationImpression -> state.copy(
shoppingState = state.shoppingState.copy(
recordedProductRecommendationImpressions =
state.shoppingState.recordedProductRecommendationImpressions + action.key,
),
)
}
private fun ShoppingState.updateProductCardState(key: String, value: CardState): ShoppingState =

@ -13,6 +13,7 @@ import org.mozilla.fenix.shopping.DefaultShoppingExperienceFeature
import org.mozilla.fenix.shopping.middleware.DefaultNetworkChecker
import org.mozilla.fenix.shopping.middleware.DefaultReviewQualityCheckPreferences
import org.mozilla.fenix.shopping.middleware.DefaultReviewQualityCheckService
import org.mozilla.fenix.shopping.middleware.DefaultReviewQualityCheckTelemetryService
import org.mozilla.fenix.shopping.middleware.DefaultReviewQualityCheckVendorsService
import org.mozilla.fenix.shopping.middleware.GetReviewQualityCheckSumoUrl
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNavigationMiddleware
@ -47,7 +48,7 @@ object ReviewQualityCheckMiddlewareProvider {
providePreferencesMiddleware(settings, browserStore, appStore, scope),
provideNetworkMiddleware(browserStore, context, scope),
provideNavigationMiddleware(TabsUseCases.SelectOrAddUseCase(browserStore), context),
provideTelemetryMiddleware(),
provideTelemetryMiddleware(browserStore, appStore, scope),
)
private fun providePreferencesMiddleware(
@ -81,6 +82,15 @@ object ReviewQualityCheckMiddlewareProvider {
GetReviewQualityCheckSumoUrl(context),
)
private fun provideTelemetryMiddleware() =
ReviewQualityCheckTelemetryMiddleware()
private fun provideTelemetryMiddleware(
browserStore: BrowserStore,
appStore: AppStore,
scope: CoroutineScope,
) =
ReviewQualityCheckTelemetryMiddleware(
telemetryService = DefaultReviewQualityCheckTelemetryService(browserStore),
browserStore = browserStore,
appStore = appStore,
scope = scope,
)
}

@ -82,14 +82,6 @@ class ReviewQualityCheckNetworkMiddleware(
store.updateRecommendedProductState()
}
}
is ReviewQualityCheckAction.RecommendedProductClick -> {
reviewQualityCheckService.recordRecommendedProductClick(action.productAid)
}
is ReviewQualityCheckAction.RecommendedProductImpression -> {
reviewQualityCheckService.recordRecommendedProductImpression(action.productAid)
}
}
}
}

@ -48,16 +48,6 @@ interface ReviewQualityCheckService {
*/
suspend fun productRecommendation(shouldRecordAvailableTelemetry: Boolean): ProductRecommendation?
/**
* Sends a click attribution event for a given product aid.
*/
suspend fun recordRecommendedProductClick(productAid: String)
/**
* Sends an impression attribution event for a given product aid.
*/
suspend fun recordRecommendedProductImpression(productAid: String)
/**
* Reports that a product is back in stock.
*
@ -170,39 +160,6 @@ class DefaultReviewQualityCheckService(
}
}
override suspend fun recordRecommendedProductClick(productAid: String) =
withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
browserStore.state.selectedTab?.engineState?.engineSession?.sendClickAttributionEvent(
aid = productAid,
onResult = {
continuation.resume(Unit)
},
onException = {
logger.error("Error sending click attribution event", it)
continuation.resume(Unit)
},
)
}
}
override suspend fun recordRecommendedProductImpression(productAid: String) {
withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
browserStore.state.selectedTab?.engineState?.engineSession?.sendImpressionAttributionEvent(
aid = productAid,
onResult = {
continuation.resume(Unit)
},
onException = {
logger.error("Error sending impression attribution event", it)
continuation.resume(Unit)
},
)
}
}
}
override suspend fun reportBackInStock(): ReportBackInStockStatusDto? = withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
browserStore.state.selectedTab?.let { tab ->

@ -4,10 +4,17 @@
package org.mozilla.fenix.shopping.middleware
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.MiddlewareContext
import mozilla.components.lib.state.Store
import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.GleanMetrics.ShoppingSettings
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction.ShoppingAction
import org.mozilla.fenix.components.appstate.shopping.ShoppingState
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckMiddleware
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
@ -18,8 +25,18 @@ private const val ACTION_DISABLED = "disabled"
/**
* Middleware that captures telemetry events for the review quality check feature.
*
* @param telemetryService The service that handles telemetry events for review checker.
* @param browserStore The [BrowserStore] instance to access the current tab.
* @param appStore The [AppStore] instance to access [ShoppingState].
* @param scope The [CoroutineScope] to use for launching coroutines.
*/
class ReviewQualityCheckTelemetryMiddleware : ReviewQualityCheckMiddleware {
class ReviewQualityCheckTelemetryMiddleware(
private val telemetryService: ReviewQualityCheckTelemetryService,
private val browserStore: BrowserStore,
private val appStore: AppStore,
private val scope: CoroutineScope,
) : ReviewQualityCheckMiddleware {
override fun invoke(
context: MiddlewareContext<ReviewQualityCheckState, ReviewQualityCheckAction>,
@ -129,11 +146,34 @@ class ReviewQualityCheckTelemetryMiddleware : ReviewQualityCheckMiddleware {
}
is ReviewQualityCheckAction.RecommendedProductImpression -> {
Shopping.surfaceAdsImpression.record()
browserStore.state.selectedTab?.let { tabSessionState ->
val key = ShoppingState.ProductRecommendationImpressionKey(
tabId = tabSessionState.id,
productUrl = tabSessionState.content.url,
aid = action.productAid,
)
val recordedImpressions =
appStore.state.shoppingState.recordedProductRecommendationImpressions
if (!recordedImpressions.contains(key)) {
Shopping.surfaceAdsImpression.record()
scope.launch {
val result =
telemetryService.recordRecommendedProductImpression(action.productAid)
if (result != null) {
appStore.dispatch(ShoppingAction.ProductRecommendationImpression(key))
}
}
}
}
}
is ReviewQualityCheckAction.RecommendedProductClick -> {
Shopping.surfaceAdsClicked.record()
scope.launch {
telemetryService.recordRecommendedProductClick(action.productAid)
}
}
ReviewQualityCheckAction.ToggleProductRecommendation -> {

@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.middleware
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.support.base.log.logger.Logger
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
/**
* Service that handles telemetry events for review checker.
*/
interface ReviewQualityCheckTelemetryService {
/**
* Sends a click attribution event for a given product aid.
*/
suspend fun recordRecommendedProductClick(productAid: String): Unit?
/**
* Sends an impression attribution event for a given product aid.
*/
suspend fun recordRecommendedProductImpression(productAid: String): Unit?
}
/**
* Service that handles the network requests for the review quality check feature.
*
* @param browserStore Reference to the application's [BrowserStore] to access state.
*/
class DefaultReviewQualityCheckTelemetryService(
private val browserStore: BrowserStore,
) : ReviewQualityCheckTelemetryService {
private val logger = Logger(TAG)
override suspend fun recordRecommendedProductClick(productAid: String) =
withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
browserStore.state.selectedTab?.engineState?.engineSession?.sendClickAttributionEvent(
aid = productAid,
onResult = {
continuation.resume(Unit)
},
onException = {
logger.error("Error sending click attribution event", it)
continuation.resume(null)
},
)
}
}
override suspend fun recordRecommendedProductImpression(productAid: String) =
withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
browserStore.state.selectedTab?.engineState?.engineSession?.sendImpressionAttributionEvent(
aid = productAid,
onResult = {
continuation.resume(Unit)
},
onException = {
logger.error("Error sending impression attribution event", it)
continuation.resume(null)
},
)
}
}
companion object {
private const val TAG = "ReviewQualityCheckTelemetryService"
}
}

@ -147,7 +147,7 @@ sealed interface ReviewQualityCheckAction : Action {
data class RecommendedProductClick(
val productAid: String,
val productUrl: String,
) : NavigationMiddlewareAction, NetworkAction, TelemetryAction
) : NavigationMiddlewareAction, TelemetryAction
/**
* Triggered when the user views the recommended product.
@ -156,7 +156,7 @@ sealed interface ReviewQualityCheckAction : Action {
*/
data class RecommendedProductImpression(
val productAid: String,
) : NetworkAction, TelemetryAction
) : TelemetryAction
/**
* Triggered when the user clicks on learn more link on the explainer card.

@ -203,4 +203,48 @@ class ShoppingActionTest {
assertEquals(expected, store.state.shoppingState)
}
@Test
fun `WHEN product recommendation impression is recorded THEN state should reflect that`() {
val store = AppStore(
initialState = AppState(
shoppingState = ShoppingState(
recordedProductRecommendationImpressions = setOf(
ShoppingState.ProductRecommendationImpressionKey(
productUrl = "pdp",
tabId = "1",
aid = "aid",
),
),
),
),
)
store.dispatch(
AppAction.ShoppingAction.ProductRecommendationImpression(
key = ShoppingState.ProductRecommendationImpressionKey(
productUrl = "pdp2",
tabId = "2",
aid = "aid2",
),
),
).joinBlocking()
val expected = ShoppingState(
recordedProductRecommendationImpressions = setOf(
ShoppingState.ProductRecommendationImpressionKey(
productUrl = "pdp",
tabId = "1",
aid = "aid",
),
ShoppingState.ProductRecommendationImpressionKey(
productUrl = "pdp2",
tabId = "2",
aid = "aid2",
),
),
)
assertEquals(expected, store.state.shoppingState)
}
}

@ -16,8 +16,6 @@ class FakeReviewQualityCheckService(
private val reanalysis: AnalysisStatusDto? = null,
private val statusProgress: () -> AnalysisStatusProgressDto? = { null },
private val productRecommendation: () -> ProductRecommendation? = { null },
private val recordClick: (String) -> Unit = {},
private val recordImpression: (String) -> Unit = {},
private val report: ReportBackInStockStatusDto? = null,
) : ReviewQualityCheckService {
@ -39,13 +37,5 @@ class FakeReviewQualityCheckService(
return productRecommendation.invoke()
}
override suspend fun recordRecommendedProductClick(productAid: String) {
recordClick(productAid)
}
override suspend fun recordRecommendedProductImpression(productAid: String) {
recordImpression(productAid)
}
override suspend fun reportBackInStock(): ReportBackInStockStatusDto? = report
}

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.fake
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckTelemetryService
class FakeReviewQualityCheckTelemetryService(
private val recordClick: (String) -> Unit = {},
private val recordImpression: (String) -> Unit = {},
) : ReviewQualityCheckTelemetryService {
override suspend fun recordRecommendedProductClick(productAid: String) {
return recordClick.invoke(productAid)
}
override suspend fun recordRecommendedProductImpression(productAid: String) {
return recordImpression.invoke(productAid)
}
}

@ -4,10 +4,15 @@
package org.mozilla.fenix.shopping.middleware
import kotlinx.coroutines.test.runTest
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.robolectric.testContext
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@ -16,8 +21,12 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.shopping.ShoppingState
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.shopping.ProductAnalysisTestData
import org.mozilla.fenix.shopping.fake.FakeReviewQualityCheckTelemetryService
import org.mozilla.fenix.shopping.store.BottomSheetDismissSource
import org.mozilla.fenix.shopping.store.BottomSheetViewState
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
@ -31,14 +40,15 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
@get:Rule
val coroutinesTestRule = MainCoroutineRule()
private lateinit var store: ReviewQualityCheckStore
@Before
fun setup() {
store = ReviewQualityCheckStore(
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(),
),
middleware = provideTelemetryMiddleware(),
)
store.waitUntilIdle()
}
@ -53,18 +63,23 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
@Test
fun `WHEN the bottom sheet is closed THEN the bottom sheet closed event is recorded`() {
store.dispatch(ReviewQualityCheckAction.BottomSheetClosed(BottomSheetDismissSource.CLICK_OUTSIDE)).joinBlocking()
store.dispatch(ReviewQualityCheckAction.BottomSheetClosed(BottomSheetDismissSource.CLICK_OUTSIDE))
.joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceClosed.testGetValue())
val event = Shopping.surfaceClosed.testGetValue()!!
assertEquals(1, event.size)
assertEquals(BottomSheetDismissSource.CLICK_OUTSIDE.sourceName, event.single().extra?.getValue("source"))
assertEquals(
BottomSheetDismissSource.CLICK_OUTSIDE.sourceName,
event.single().extra?.getValue("source"),
)
}
@Test
fun `WHEN the bottom sheet is displayed THEN the bottom sheet displayed event is recorded`() {
store.dispatch(ReviewQualityCheckAction.BottomSheetDisplayed(BottomSheetViewState.HALF_VIEW)).joinBlocking()
store.dispatch(ReviewQualityCheckAction.BottomSheetDisplayed(BottomSheetViewState.HALF_VIEW))
.joinBlocking()
store.waitUntilIdle()
assertNotNull(Shopping.surfaceDisplayed.testGetValue())
@ -122,9 +137,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isHighlightsExpanded = false,
),
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(),
),
middleware = provideTelemetryMiddleware(),
)
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.ExpandCollapseHighlights).joinBlocking()
@ -142,9 +155,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isHighlightsExpanded = true,
),
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(),
),
middleware = provideTelemetryMiddleware(),
)
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.ExpandCollapseHighlights).joinBlocking()
@ -162,9 +173,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isSettingsExpanded = false,
),
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(),
),
middleware = provideTelemetryMiddleware(),
)
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.ExpandCollapseSettings).joinBlocking()
@ -182,9 +191,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isSettingsExpanded = true,
),
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(),
),
middleware = provideTelemetryMiddleware(),
)
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.ExpandCollapseSettings).joinBlocking()
@ -253,7 +260,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.BEST_BUY,
),
middleware = listOf(ReviewQualityCheckTelemetryMiddleware()),
middleware = provideTelemetryMiddleware(),
)
tested.dispatch(
@ -278,7 +285,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.BEST_BUY,
),
middleware = listOf(ReviewQualityCheckTelemetryMiddleware()),
middleware = provideTelemetryMiddleware(),
)
tested.dispatch(
@ -302,7 +309,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.BEST_BUY,
),
middleware = listOf(ReviewQualityCheckTelemetryMiddleware()),
middleware = provideTelemetryMiddleware(),
)
tested.dispatch(
@ -317,20 +324,136 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
}
@Test
fun `WHEN a product recommendation is visible for more than one and a half seconds THEN ad impression telemetry probe is sent`() {
store.dispatch(ReviewQualityCheckAction.RecommendedProductImpression("")).joinBlocking()
store.waitUntilIdle()
fun `GIVEN a recommendation impression action is dispatched WHEN app state does not contain key with tab id, product url and aid THEN ad impression telemetry probe is sent`() =
runTest {
var productViewed: String? = null
val tested = ReviewQualityCheckStore(
middleware = provideTelemetryMiddleware(
reviewQualityCheckTelemetryService = FakeReviewQualityCheckTelemetryService(
recordImpression = {
productViewed = it
},
),
browserState = BrowserState(
selectedTabId = "tabId",
tabs = listOf(
createTab(
id = "tabId",
url = "pdp",
),
),
),
),
)
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.RecommendedProductImpression("productId"))
.joinBlocking()
tested.waitUntilIdle()
assertNotNull(Shopping.surfaceAdsImpression.testGetValue())
assertEquals("productId", productViewed)
}
assertNotNull(Shopping.surfaceAdsImpression.testGetValue())
}
@Test
fun `WHEN recommendation impression action is dispatched many times and app state does not initially contain key with tab id, product url and aid THEN ad impression telemetry probe is sent only once`() =
runTest {
var productViewed: String? = null
var impressionCount = 0
val appStore = AppStore()
val tested = ReviewQualityCheckStore(
middleware = provideTelemetryMiddleware(
reviewQualityCheckTelemetryService = FakeReviewQualityCheckTelemetryService(
recordImpression = {
productViewed = it
impressionCount++
},
),
browserState = BrowserState(
selectedTabId = "tabId",
tabs = listOf(
createTab(
id = "tabId",
url = "pdp",
),
),
),
appStore = appStore,
),
)
tested.waitUntilIdle()
for (i in 1..100) {
tested.dispatch(ReviewQualityCheckAction.RecommendedProductImpression("productId"))
.joinBlocking()
tested.waitUntilIdle()
appStore.waitUntilIdle()
}
assertNotNull(Shopping.surfaceAdsImpression.testGetValue())
assertEquals("productId", productViewed)
assertEquals(1, impressionCount)
}
@Test
fun `WHEN a product recommendation is clicked THEN the ad clicked telemetry probe is sent`() {
store.dispatch(ReviewQualityCheckAction.RecommendedProductClick("", "")).joinBlocking()
store.waitUntilIdle()
fun `GIVEN a recommendation impression action is dispatched WHEN app state contains key with tab id, product url and aid THEN ad impression telemetry probe is NOT sent`() =
runTest {
var productViewed: String? = null
val tested = ReviewQualityCheckStore(
middleware = provideTelemetryMiddleware(
reviewQualityCheckTelemetryService = FakeReviewQualityCheckTelemetryService(
recordImpression = { productViewed = it },
),
browserState = BrowserState(
selectedTabId = "tabId",
tabs = listOf(
createTab(
id = "tabId",
url = "pdp",
),
),
),
appStore = AppStore(
AppState(
shoppingState = ShoppingState(
recordedProductRecommendationImpressions = setOf(
ShoppingState.ProductRecommendationImpressionKey(
tabId = "tabId",
productUrl = "pdp",
aid = "productId",
),
),
),
),
),
),
)
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.RecommendedProductImpression("productId"))
.joinBlocking()
tested.waitUntilIdle()
assertNull(Shopping.surfaceAdsImpression.testGetValue())
assertNull(productViewed)
}
assertNotNull(Shopping.surfaceAdsClicked.testGetValue())
}
@Test
fun `WHEN a product recommendation is clicked THEN the ad clicked telemetry probe is sent`() =
runTest {
var productClicked: String? = null
val tested = ReviewQualityCheckStore(
middleware = provideTelemetryMiddleware(
reviewQualityCheckTelemetryService = FakeReviewQualityCheckTelemetryService(
recordClick = { productClicked = it },
),
),
)
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.RecommendedProductClick("productId", ""))
.joinBlocking()
tested.waitUntilIdle()
assertNotNull(Shopping.surfaceAdsClicked.testGetValue())
assertEquals("productId", productClicked)
}
@Test
fun `GIVEN the user has opted in WHEN the user switches product recommendations on THEN send enabled product recommendations toggled telemetry probe`() {
@ -341,15 +464,16 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isHighlightsExpanded = false,
),
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(),
),
middleware = provideTelemetryMiddleware(),
)
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
tested.waitUntilIdle()
assertEquals("enabled", Shopping.surfaceAdsSettingToggled.testGetValue()!!.first().extra!!["action"])
assertEquals(
"enabled",
Shopping.surfaceAdsSettingToggled.testGetValue()!!.first().extra!!["action"],
)
}
@Test
@ -361,14 +485,28 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isHighlightsExpanded = false,
),
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(),
),
middleware = provideTelemetryMiddleware(),
)
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
tested.waitUntilIdle()
assertEquals("disabled", Shopping.surfaceAdsSettingToggled.testGetValue()!!.first().extra!!["action"])
assertEquals(
"disabled",
Shopping.surfaceAdsSettingToggled.testGetValue()!!.first().extra!!["action"],
)
}
private fun provideTelemetryMiddleware(
reviewQualityCheckTelemetryService: FakeReviewQualityCheckTelemetryService = FakeReviewQualityCheckTelemetryService(),
browserState: BrowserState = BrowserState(),
appStore: AppStore = AppStore(),
) = listOf(
ReviewQualityCheckTelemetryMiddleware(
reviewQualityCheckTelemetryService,
BrowserStore(browserState),
appStore,
coroutinesTestRule.scope,
),
)
}

@ -1618,81 +1618,6 @@ class ReviewQualityCheckStoreTest {
captureActionsMiddleware.assertNotDispatched(ReviewQualityCheckAction.UpdateRecommendedProduct::class)
}
@Test
fun `GIVEN product recommendations are enabled WHEN recommended product is clicked THEN click event is recorded`() =
runTest {
var productClicked: String? = null
val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = true,
isProductRecommendationsEnabled = true,
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = {
ProductRecommendationTestData.productRecommendation(
aid = "342",
)
},
recordClick = {
productClicked = it
},
),
),
)
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
tested.dispatch(
ReviewQualityCheckAction.RecommendedProductClick(
productAid = "342",
productUrl = "https://test.com",
),
).joinBlocking()
assertEquals("342", productClicked)
}
@Test
fun `GIVEN product recommendations are enabled WHEN recommended product is viewed THEN impression event is recorded`() =
runTest {
var productViewed: String? = null
val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = true,
isProductRecommendationsEnabled = true,
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = {
ProductRecommendationTestData.productRecommendation(
aid = "342",
)
},
recordImpression = {
productViewed = it
},
),
),
)
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
tested.dispatch(
ReviewQualityCheckAction.RecommendedProductImpression(productAid = "342"),
).joinBlocking()
assertEquals("342", productViewed)
}
private fun provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences: ReviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(),
reviewQualityCheckVendorsService: FakeReviewQualityCheckVendorsService = FakeReviewQualityCheckVendorsService(),

Loading…
Cancel
Save