Bug 1866992 - Add product recommendations exposure telemetry

(cherry picked from commit daf7931aee0e24763e302203ddf36b6a8e434d8a)
fenix/121.0
rahulsainani 6 months ago committed by Ryan VanderMeulen
parent e2cec70972
commit 41140c6178

@ -10923,6 +10923,48 @@ shopping:
metadata:
tags:
- Shopping
ads_exposure:
type: event
description: |
On a supported product page, the review checker showed analysis,
and the ads exposure pref was enabled, or review checker ads were enabled,
and when we tried to fetch an ad from the ad server, an ad was available.
Does not indicate whether the ad was actually shown.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1866992
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/4622#issuecomment-1829905076
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Shopping
surface_no_ads_available:
type: event
description: |
On a supported product page, the review checker
showed analysis, and review checker ads were enabled,
but when we tried to fetch an ad from the ad server,
no ad was available.
send_in_pings:
- events
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1866992
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/4622#issuecomment-1829905076
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Shopping
shopping.settings:
component_opted_out:

@ -405,11 +405,16 @@ features:
description: if true, recommended products feature is enabled to be shown to the user based on their preference.
type: Boolean
default: false
product-recommendations-exposure:
description: if true, we want to record recommended products inventory for opted-in users, even if product recommendations are disabled.
type: Boolean
default: false
defaults:
- channel: developer
value:
enabled: true
product-recommendations: true
product-recommendations-exposure: true
print:
description: A feature for printing from the share or browser menu.

@ -15,6 +15,11 @@ interface ShoppingExperienceFeature {
* Returns true if the shopping experience feature is enabled.
*/
val isEnabled: Boolean
/**
* Returns true if product recommendations exposure nimbus flag is enabled.
*/
val isProductRecommendationsExposureEnabled: Boolean
}
/**
@ -24,4 +29,7 @@ class DefaultShoppingExperienceFeature : ShoppingExperienceFeature {
override val isEnabled
get() = FxNimbus.features.shoppingExperience.value().enabled
override val isProductRecommendationsExposureEnabled: Boolean
get() = FxNimbus.features.shoppingExperience.value().productRecommendationsExposure
}

@ -9,6 +9,7 @@ import kotlinx.coroutines.CoroutineScope
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.tabs.TabsUseCases
import org.mozilla.fenix.components.AppStore
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
@ -58,6 +59,7 @@ object ReviewQualityCheckMiddlewareProvider {
reviewQualityCheckPreferences = DefaultReviewQualityCheckPreferences(settings),
reviewQualityCheckVendorsService = DefaultReviewQualityCheckVendorsService(browserStore),
appStore = appStore,
shoppingExperienceFeature = DefaultShoppingExperienceFeature(),
scope = scope,
)

@ -170,10 +170,15 @@ class ReviewQualityCheckNetworkMiddleware(
private suspend fun Store<ReviewQualityCheckState, ReviewQualityCheckAction>.updateRecommendedProductState() {
val currentState = state
if (currentState is ReviewQualityCheckState.OptedIn &&
currentState.productRecommendationsPreference == true
(currentState.productRecommendationsExposure || (currentState.productRecommendationsPreference == true))
) {
reviewQualityCheckService.productRecommendation().toRecommendedProductState().also {
dispatch(UpdateRecommendedProduct(it))
val productRecommendation = reviewQualityCheckService.productRecommendation(
currentState.productRecommendationsPreference ?: false,
)
if (currentState.productRecommendationsPreference == true) {
productRecommendation.toRecommendedProductState().also {
dispatch(UpdateRecommendedProduct(it))
}
}
}
}

@ -16,6 +16,7 @@ import org.mozilla.fenix.components.appstate.AppAction.ShoppingAction.InfoCardEx
import org.mozilla.fenix.components.appstate.AppAction.ShoppingAction.SettingsCardExpanded
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.shopping.ShoppingState.CardState
import org.mozilla.fenix.shopping.ShoppingExperienceFeature
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckMiddleware
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
@ -29,12 +30,14 @@ import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.OptedIn
* @param reviewQualityCheckVendorsService The [ReviewQualityCheckVendorsService] instance for
* getting the list of product vendors.
* @param appStore The [AppStore] instance for dispatching [ShoppingAction]s.
* @param shoppingExperienceFeature The [ShoppingExperienceFeature] instance to get feature flags.
* @param scope The [CoroutineScope] to use for launching coroutines.
*/
class ReviewQualityCheckPreferencesMiddleware(
private val reviewQualityCheckPreferences: ReviewQualityCheckPreferences,
private val reviewQualityCheckVendorsService: ReviewQualityCheckVendorsService,
private val appStore: AppStore,
private val shoppingExperienceFeature: ShoppingExperienceFeature,
private val scope: CoroutineScope,
) : ReviewQualityCheckMiddleware {
@ -75,6 +78,8 @@ class ReviewQualityCheckPreferencesMiddleware(
ReviewQualityCheckAction.OptInCompleted(
isProductRecommendationsEnabled = isProductRecommendationsEnabled,
productRecommendationsExposure =
shoppingExperienceFeature.isProductRecommendationsExposureEnabled,
productVendor = reviewQualityCheckVendorsService.productVendor(),
isHighlightsExpanded = savedCardState.isHighlightsExpanded,
isInfoExpanded = savedCardState.isInfoExpanded,
@ -95,6 +100,8 @@ class ReviewQualityCheckPreferencesMiddleware(
store.dispatch(
ReviewQualityCheckAction.OptInCompleted(
isProductRecommendationsEnabled = isProductRecommendationsEnabled,
productRecommendationsExposure =
shoppingExperienceFeature.isProductRecommendationsExposureEnabled,
productVendor = reviewQualityCheckVendorsService.productVendor(),
isHighlightsExpanded = false,
isInfoExpanded = false,

@ -11,6 +11,7 @@ import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.shopping.ProductAnalysis
import mozilla.components.concept.engine.shopping.ProductRecommendation
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.GleanMetrics.Shopping
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@ -50,7 +51,7 @@ interface ReviewQualityCheckService {
*
* @return [ProductRecommendation] if request succeeds, null otherwise.
*/
suspend fun productRecommendation(): ProductRecommendation?
suspend fun productRecommendation(shouldRecordAvailableTelemetry: Boolean): ProductRecommendation?
/**
* Sends a click attribution event for a given product aid.
@ -128,13 +129,20 @@ class DefaultReviewQualityCheckService(
override fun selectedTabUrl(): String? =
browserStore.state.selectedTab?.content?.url
override suspend fun productRecommendation(): ProductRecommendation? =
override suspend fun productRecommendation(shouldRecordAvailableTelemetry: Boolean): ProductRecommendation? =
withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
browserStore.state.selectedTab?.let { tab ->
tab.engineState.engineSession?.requestProductRecommendations(
url = tab.content.url,
onResult = {
if (it.isEmpty()) {
if (shouldRecordAvailableTelemetry) {
Shopping.surfaceNoAdsAvailable.record()
}
} else {
Shopping.adsExposure.record()
}
// Return the first available recommendation since ui requires only
// one recommendation.
continuation.resume(it.firstOrNull())

@ -63,6 +63,7 @@ sealed interface ReviewQualityCheckAction : Action {
*
* @property isProductRecommendationsEnabled Reflects the user preference update to display
* recommended product. Null when product recommendations feature is disabled.
* @property productRecommendationsExposure Whether product recommendations exposure is enabled.
* @property productVendor The vendor of the product.
* @property isHighlightsExpanded Whether the highlights card should be expanded.
* @property isInfoExpanded Whether the info card should be expanded.
@ -70,6 +71,7 @@ sealed interface ReviewQualityCheckAction : Action {
*/
data class OptInCompleted(
val isProductRecommendationsEnabled: Boolean?,
val productRecommendationsExposure: Boolean,
val productVendor: ReviewQualityCheckState.ProductVendor,
val isHighlightsExpanded: Boolean,
val isInfoExpanded: Boolean,

@ -41,6 +41,7 @@ sealed interface ReviewQualityCheckState : State {
* @property productRecommendationsPreference User preference whether to show product
* recommendations. True if product recommendations should be shown. Null indicates that product
* recommendations are disabled.
* @property productRecommendationsExposure Whether product recommendations exposure is enabled.
* @property productVendor The vendor of the product.
* @property isSettingsExpanded Whether or not the settings card is expanded.
* @property isInfoExpanded Whether or not the info card is expanded.
@ -49,6 +50,7 @@ sealed interface ReviewQualityCheckState : State {
data class OptedIn(
val productReviewState: ProductReviewState = ProductReviewState.Loading,
val productRecommendationsPreference: Boolean?,
val productRecommendationsExposure: Boolean,
val productVendor: ProductVendor,
val isSettingsExpanded: Boolean = false,
val isInfoExpanded: Boolean = false,

@ -48,6 +48,7 @@ private fun mapStateForUpdateAction(
if (state is ReviewQualityCheckState.OptedIn) {
state.copy(
productRecommendationsPreference = action.isProductRecommendationsEnabled,
productRecommendationsExposure = action.productRecommendationsExposure,
isHighlightsExpanded = action.isHighlightsExpanded,
isInfoExpanded = action.isInfoExpanded,
isSettingsExpanded = action.isSettingsExpanded,
@ -55,6 +56,7 @@ private fun mapStateForUpdateAction(
} else {
ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = action.isProductRecommendationsEnabled,
productRecommendationsExposure = action.productRecommendationsExposure,
productVendor = action.productVendor,
isHighlightsExpanded = action.isHighlightsExpanded,
isInfoExpanded = action.isInfoExpanded,

@ -34,6 +34,7 @@ class ReviewQualityCheckBottomSheetStateFeatureTest {
store.dispatch(
ReviewQualityCheckAction.OptInCompleted(
isProductRecommendationsEnabled = true,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.WALMART,
isHighlightsExpanded = false,
isInfoExpanded = false,

@ -14,7 +14,7 @@ class FakeReviewQualityCheckService(
private val reanalysis: AnalysisStatusDto? = null,
private val status: AnalysisStatusDto? = null,
private val selectedTabUrl: String? = null,
private val productRecommendation: ProductRecommendation? = null,
private val productRecommendation: () -> ProductRecommendation? = { null },
private val recordClick: (String) -> Unit = {},
private val recordImpression: (String) -> Unit = {},
) : ReviewQualityCheckService {
@ -33,7 +33,9 @@ class FakeReviewQualityCheckService(
override fun selectedTabUrl(): String? = selectedTabUrl
override suspend fun productRecommendation(): ProductRecommendation? = productRecommendation
override suspend fun productRecommendation(shouldRecordAvailableTelemetry: Boolean): ProductRecommendation? {
return productRecommendation.invoke()
}
override suspend fun recordRecommendedProductClick(productAid: String) {
recordClick(productAid)

@ -8,8 +8,12 @@ import org.mozilla.fenix.shopping.ShoppingExperienceFeature
class FakeShoppingExperienceFeature(
private val enabled: Boolean = true,
private val productRecommendationsExposureEnabled: Boolean = true,
) : ShoppingExperienceFeature {
override val isEnabled: Boolean
get() = enabled
override val isProductRecommendationsExposureEnabled: Boolean
get() = productRecommendationsExposureEnabled
}

@ -12,18 +12,30 @@ import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.shopping.ProductAnalysis
import mozilla.components.concept.engine.shopping.ProductRecommendation
import mozilla.components.service.glean.testing.GleanTestRule
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
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.shopping.ProductAnalysisTestData
import org.mozilla.fenix.shopping.ProductRecommendationTestData
@RunWith(FenixRobolectricTestRunner::class)
class DefaultReviewQualityCheckServiceTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule()
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
@Test
fun `GIVEN fetch is called WHEN onResult is invoked with the expected type THEN product analysis returns the same data`() =
runTest {
@ -110,4 +122,121 @@ class DefaultReviewQualityCheckServiceTest {
assertEquals(expected, actual)
}
@Test
fun `GIVEN product recommendations is called WHEN onResult is invoked with the result THEN recommendations returns the data and exposure is called`() =
runTest {
val engineSession = mockk<EngineSession>()
val expected = ProductRecommendationTestData.productRecommendation()
val productRecommendations = listOf(expected)
every {
engineSession.requestProductRecommendations(any(), any(), any())
}.answers {
secondArg<(List<ProductRecommendation>) -> Unit>().invoke(productRecommendations)
}
val tab = createTab(
url = "https://www.shopping.org/product",
id = "test-tab",
engineSession = engineSession,
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = DefaultReviewQualityCheckService(BrowserStore(browserState))
val actual = tested.productRecommendation(false)
assertEquals(expected, actual)
assertNotNull(Shopping.adsExposure.testGetValue())
}
@Test
fun `GIVEN product recommendations is called WHEN onResult is invoked with a empty list and telemetry should be recorded THEN recommendations returns null and no ads available event is called`() =
runTest {
val engineSession = mockk<EngineSession>()
every {
engineSession.requestProductRecommendations(any(), any(), any())
}.answers {
secondArg<(List<ProductRecommendation>) -> Unit>().invoke(emptyList())
}
val tab = createTab(
url = "https://www.shopping.org/product",
id = "test-tab",
engineSession = engineSession,
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = DefaultReviewQualityCheckService(BrowserStore(browserState))
val actual = tested.productRecommendation(true)
assertNull(actual)
assertNotNull(Shopping.surfaceNoAdsAvailable.testGetValue())
}
@Test
fun `GIVEN product recommendations is called WHEN onResult is invoked with a empty list and telemetry should not be recorded THEN recommendations returns null and no ads available event is not called`() =
runTest {
val engineSession = mockk<EngineSession>()
every {
engineSession.requestProductRecommendations(any(), any(), any())
}.answers {
secondArg<(List<ProductRecommendation>) -> Unit>().invoke(emptyList())
}
val tab = createTab(
url = "https://www.shopping.org/product",
id = "test-tab",
engineSession = engineSession,
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = DefaultReviewQualityCheckService(BrowserStore(browserState))
val actual = tested.productRecommendation(false)
assertNull(actual)
assertNull(Shopping.surfaceNoAdsAvailable.testGetValue())
}
@Test
fun `GIVEN product recommendations is called WHEN onException is invoked THEN recommendations returns null`() =
runTest {
val engineSession = mockk<EngineSession>()
every {
engineSession.requestProductRecommendations(any(), any(), any())
}.answers {
thirdArg<(Throwable) -> Unit>().invoke(RuntimeException())
}
val tab = createTab(
url = "https://www.shopping.org/product",
id = "test-tab",
engineSession = engineSession,
)
val browserState = BrowserState(
tabs = listOf(tab),
selectedTabId = tab.id,
)
val tested = DefaultReviewQualityCheckService(BrowserStore(browserState))
val actual = tested.productRecommendation(false)
assertNull(actual)
}
}

@ -11,7 +11,7 @@ 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 org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
@ -67,8 +67,8 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
assertNotNull(Shopping.surfaceClosed.testGetValue())
val event = Shopping.surfaceClosed.testGetValue()!!
Assert.assertEquals(1, event.size)
Assert.assertEquals(BottomSheetDismissSource.CLICK_OUTSIDE.sourceName, event.single().extra?.getValue("source"))
assertEquals(1, event.size)
assertEquals(BottomSheetDismissSource.CLICK_OUTSIDE.sourceName, event.single().extra?.getValue("source"))
}
@Test
@ -78,8 +78,8 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
assertNotNull(Shopping.surfaceDisplayed.testGetValue())
val event = Shopping.surfaceDisplayed.testGetValue()!!
Assert.assertEquals(1, event.size)
Assert.assertEquals(BottomSheetViewState.HALF_VIEW.state, event.single().extra?.getValue("view"))
assertEquals(1, event.size)
assertEquals(BottomSheetViewState.HALF_VIEW.state, event.single().extra?.getValue("view"))
}
@Test
@ -127,6 +127,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
val tested = ReviewQualityCheckStore(
initialState = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = true,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isHighlightsExpanded = false,
),
@ -146,6 +147,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
val tested = ReviewQualityCheckStore(
initialState = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = true,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isHighlightsExpanded = true,
),
@ -165,6 +167,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
val tested = ReviewQualityCheckStore(
initialState = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = true,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isSettingsExpanded = false,
),
@ -184,6 +187,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
val tested = ReviewQualityCheckStore(
initialState = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = true,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isSettingsExpanded = true,
),
@ -261,6 +265,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
analysisStatus = AnalysisPresent.AnalysisStatus.UP_TO_DATE,
),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
),
middleware = listOf(
@ -318,6 +323,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
analysisStatus = AnalysisPresent.AnalysisStatus.NEEDS_ANALYSIS,
),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
),
middleware = listOf(
@ -355,6 +361,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
val tested = ReviewQualityCheckStore(
initialState = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isHighlightsExpanded = false,
),
@ -366,7 +373,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
tested.waitUntilIdle()
assertNotNull(Shopping.SurfaceAdsSettingToggledExtra("enabled"))
assertEquals("enabled", Shopping.surfaceAdsSettingToggled.testGetValue()!!.first().extra!!["action"])
}
@Test
@ -374,6 +381,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
val tested = ReviewQualityCheckStore(
initialState = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = true,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON,
isHighlightsExpanded = false,
),
@ -385,6 +393,6 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
tested.waitUntilIdle()
assertNotNull(Shopping.SurfaceAdsSettingToggledExtra("disabled"))
assertEquals("disabled", Shopping.surfaceAdsSettingToggled.testGetValue()!!.first().extra!!["action"])
}
}

@ -25,10 +25,12 @@ import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.shopping.ShoppingState
import org.mozilla.fenix.shopping.ProductAnalysisTestData
import org.mozilla.fenix.shopping.ProductRecommendationTestData
import org.mozilla.fenix.shopping.ShoppingExperienceFeature
import org.mozilla.fenix.shopping.fake.FakeNetworkChecker
import org.mozilla.fenix.shopping.fake.FakeReviewQualityCheckPreferences
import org.mozilla.fenix.shopping.fake.FakeReviewQualityCheckService
import org.mozilla.fenix.shopping.fake.FakeReviewQualityCheckVendorsService
import org.mozilla.fenix.shopping.fake.FakeShoppingExperienceFeature
import org.mozilla.fenix.shopping.middleware.AnalysisStatusDto
import org.mozilla.fenix.shopping.middleware.NetworkChecker
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNetworkMiddleware
@ -99,6 +101,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -143,6 +146,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = null,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -172,6 +176,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = true,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -188,7 +193,7 @@ class ReviewQualityCheckStoreTest {
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = ProductRecommendationTestData.productRecommendation(),
productRecommendation = { ProductRecommendationTestData.productRecommendation() },
),
),
)
@ -203,6 +208,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
productReviewState = ProductAnalysisTestData.analysisPresent(
recommendedProductState = ReviewQualityCheckState.RecommendedProductState.Initial,
@ -246,6 +252,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
isHighlightsExpanded = false,
isSettingsExpanded = true,
@ -279,6 +286,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
isSettingsExpanded = true,
)
@ -322,6 +330,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
isSettingsExpanded = false,
)
@ -356,6 +365,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
isInfoExpanded = true,
)
@ -399,6 +409,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
isInfoExpanded = false,
)
@ -434,6 +445,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
isHighlightsExpanded = true,
)
@ -478,6 +490,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
isHighlightsExpanded = false,
)
@ -509,6 +522,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ProductAnalysisTestData.analysisPresent(),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -534,6 +548,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ReviewQualityCheckState.OptedIn.ProductReviewState.Error.GenericError,
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -559,6 +574,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ReviewQualityCheckState.OptedIn.ProductReviewState.Error.NetworkError,
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -586,6 +602,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ReviewQualityCheckState.OptedIn.ProductReviewState.Error.GenericError,
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -629,6 +646,7 @@ class ReviewQualityCheckStoreTest {
analysisStatus = AnalysisStatus.NEEDS_ANALYSIS,
),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -658,6 +676,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ProductAnalysisTestData.analysisPresent(),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -691,6 +710,7 @@ class ReviewQualityCheckStoreTest {
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ReviewQualityCheckState.OptedIn.ProductReviewState.Error.NotEnoughReviews,
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -734,6 +754,7 @@ class ReviewQualityCheckStoreTest {
analysisStatus = AnalysisStatus.REANALYZING,
),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
@ -784,6 +805,7 @@ class ReviewQualityCheckStoreTest {
analysisStatus = AnalysisStatus.NEEDS_ANALYSIS,
),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -793,6 +815,7 @@ class ReviewQualityCheckStoreTest {
analysisStatus = AnalysisStatus.REANALYZING,
),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertFalse(observedState.contains(notExpected))
@ -841,7 +864,7 @@ class ReviewQualityCheckStoreTest {
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = ProductRecommendationTestData.productRecommendation(),
productRecommendation = { ProductRecommendationTestData.productRecommendation() },
),
),
)
@ -857,12 +880,138 @@ class ReviewQualityCheckStoreTest {
recommendedProductState = ProductRecommendationTestData.product(),
),
productRecommendationsPreference = true,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
}
}
@Test
fun `GIVEN product recommendations are disabled WHEN a product analysis is fetched successfully and exposure is set to true THEN product recommendation should also be fetched`() =
runTest {
setAndResetLocale {
var productRecommendationFetched = false
val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = true,
isProductRecommendationsEnabled = false,
),
shoppingExperienceFeature = FakeShoppingExperienceFeature(
productRecommendationsExposureEnabled = true,
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = {
productRecommendationFetched = true
ProductRecommendationTestData.productRecommendation()
},
),
),
)
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ProductAnalysisTestData.analysisPresent(),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
assertTrue(productRecommendationFetched)
}
}
@Test
fun `GIVEN product recommendations are disabled WHEN a product analysis is fetched successfully and exposure is set to false THEN product recommendation should not be fetched and displayed`() =
runTest {
setAndResetLocale {
var productRecommendationFetched = false
val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = true,
isProductRecommendationsEnabled = false,
),
shoppingExperienceFeature = FakeShoppingExperienceFeature(
productRecommendationsExposureEnabled = false,
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = {
productRecommendationFetched = true
ProductRecommendationTestData.productRecommendation()
},
),
),
)
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ProductAnalysisTestData.analysisPresent(),
productRecommendationsPreference = false,
productRecommendationsExposure = false,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
assertFalse(productRecommendationFetched)
}
}
@Test
fun `GIVEN product recommendations are enabled WHEN a product analysis is fetched successfully and exposure is set to false THEN product recommendation should be fetched and displayed`() =
runTest {
setAndResetLocale {
var productRecommendationFetched = false
val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = true,
isProductRecommendationsEnabled = true,
),
shoppingExperienceFeature = FakeShoppingExperienceFeature(
productRecommendationsExposureEnabled = false,
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = {
productRecommendationFetched = true
ProductRecommendationTestData.productRecommendation()
},
),
),
)
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ProductAnalysisTestData.analysisPresent(
recommendedProductState = ProductRecommendationTestData.product(),
),
productRecommendationsPreference = true,
productRecommendationsExposure = false,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
assertTrue(productRecommendationFetched)
}
}
@Test
fun `GIVEN product recommendations are enabled WHEN a product analysis is fetched successfully and product recommendation fails THEN product recommendations state should be initial`() =
runTest {
@ -874,7 +1023,7 @@ class ReviewQualityCheckStoreTest {
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = null,
productRecommendation = { null },
),
),
)
@ -890,6 +1039,7 @@ class ReviewQualityCheckStoreTest {
recommendedProductState = ReviewQualityCheckState.RecommendedProductState.Initial,
),
productRecommendationsPreference = true,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
@ -898,7 +1048,8 @@ class ReviewQualityCheckStoreTest {
@Test
fun `GIVEN product recommendations are enabled WHEN product analysis fails THEN product recommendations should not be fetched`() =
runTest {
val captureActionsMiddleware = CaptureActionsMiddleware<ReviewQualityCheckState, ReviewQualityCheckAction>()
val captureActionsMiddleware =
CaptureActionsMiddleware<ReviewQualityCheckState, ReviewQualityCheckAction>()
val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
@ -907,7 +1058,7 @@ class ReviewQualityCheckStoreTest {
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { null },
productRecommendation = ProductRecommendationTestData.productRecommendation(),
productRecommendation = { ProductRecommendationTestData.productRecommendation() },
),
) + captureActionsMiddleware,
)
@ -933,9 +1084,11 @@ class ReviewQualityCheckStoreTest {
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = ProductRecommendationTestData.productRecommendation(
aid = "342",
),
productRecommendation = {
ProductRecommendationTestData.productRecommendation(
aid = "342",
)
},
recordClick = {
productClicked = it
},
@ -970,9 +1123,11 @@ class ReviewQualityCheckStoreTest {
),
reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() },
productRecommendation = ProductRecommendationTestData.productRecommendation(
aid = "342",
),
productRecommendation = {
ProductRecommendationTestData.productRecommendation(
aid = "342",
)
},
recordImpression = {
productViewed = it
},
@ -997,6 +1152,7 @@ class ReviewQualityCheckStoreTest {
reviewQualityCheckVendorsService: FakeReviewQualityCheckVendorsService = FakeReviewQualityCheckVendorsService(),
reviewQualityCheckService: ReviewQualityCheckService = FakeReviewQualityCheckService(),
networkChecker: NetworkChecker = FakeNetworkChecker(),
shoppingExperienceFeature: ShoppingExperienceFeature = FakeShoppingExperienceFeature(),
appStore: AppStore = AppStore(),
): List<ReviewQualityCheckMiddleware> {
return listOf(
@ -1004,6 +1160,7 @@ class ReviewQualityCheckStoreTest {
reviewQualityCheckPreferences = reviewQualityCheckPreferences,
reviewQualityCheckVendorsService = reviewQualityCheckVendorsService,
appStore = appStore,
shoppingExperienceFeature = shoppingExperienceFeature,
scope = this.scope,
),
ReviewQualityCheckNetworkMiddleware(

Loading…
Cancel
Save