Bug 1847923 - Part 2: Add ReviewQualityCheckNetworkMiddleware
parent
5510dc1101
commit
4c52f74add
@ -0,0 +1,59 @@
|
||||
/* 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.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import mozilla.components.lib.state.Store
|
||||
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
|
||||
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction.FetchProductAnalysis
|
||||
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction.RetryProductAnalysis
|
||||
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
|
||||
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.OptedIn.ProductReviewState
|
||||
|
||||
/**
|
||||
* Middleware that handles network requests for the review quality check feature.
|
||||
*
|
||||
* @property reviewQualityCheckService The service that handles the network requests.
|
||||
* @property scope The [CoroutineScope] that will be used to launch coroutines.
|
||||
*/
|
||||
class ReviewQualityCheckNetworkMiddleware(
|
||||
private val reviewQualityCheckService: ReviewQualityCheckService,
|
||||
private val scope: CoroutineScope,
|
||||
) : Middleware<ReviewQualityCheckState, ReviewQualityCheckAction> {
|
||||
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<ReviewQualityCheckState, ReviewQualityCheckAction>,
|
||||
next: (ReviewQualityCheckAction) -> Unit,
|
||||
action: ReviewQualityCheckAction,
|
||||
) {
|
||||
when (action) {
|
||||
is ReviewQualityCheckAction.NetworkAction -> processAction(context.store, action)
|
||||
else -> {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
|
||||
private fun processAction(
|
||||
store: Store<ReviewQualityCheckState, ReviewQualityCheckAction>,
|
||||
action: ReviewQualityCheckAction.NetworkAction,
|
||||
) {
|
||||
when (action) {
|
||||
FetchProductAnalysis, RetryProductAnalysis -> {
|
||||
store.dispatch(ReviewQualityCheckAction.UpdateProductReview(ProductReviewState.Loading))
|
||||
|
||||
scope.launch {
|
||||
val analysis = reviewQualityCheckService.fetchProductReview()
|
||||
val productReviewState = analysis.toProductReviewState()
|
||||
store.dispatch(ReviewQualityCheckAction.UpdateProductReview(productReviewState))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/* 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.concept.engine.shopping.ProductAnalysis
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
/**
|
||||
* Service that handles the network requests for the review quality check feature.
|
||||
*/
|
||||
interface ReviewQualityCheckService {
|
||||
|
||||
/**
|
||||
* Fetches the product review for the current tab.
|
||||
*
|
||||
* @return [ProductAnalysis] if the request succeeds, null otherwise.
|
||||
*/
|
||||
suspend fun fetchProductReview(): ProductAnalysis?
|
||||
}
|
||||
|
||||
/**
|
||||
* Service that handles the network requests for the review quality check feature.
|
||||
*
|
||||
* @property browserStore Reference to the application's [BrowserStore] to access state.
|
||||
*/
|
||||
class ReviewQualityCheckServiceImpl(
|
||||
private val browserStore: BrowserStore,
|
||||
) : ReviewQualityCheckService {
|
||||
|
||||
override suspend fun fetchProductReview(): ProductAnalysis? = withContext(Dispatchers.Main) {
|
||||
suspendCoroutine { continuation ->
|
||||
browserStore.state.selectedTab?.let { tab ->
|
||||
tab.engineState.engineSession?.requestProductAnalysis(
|
||||
url = tab.content.url,
|
||||
onResult = { continuation.resume(it) },
|
||||
onException = { continuation.resume(null) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/* 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.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.concept.engine.EngineSession
|
||||
import mozilla.components.concept.engine.shopping.ProductAnalysis
|
||||
import mozilla.components.support.test.any
|
||||
import mozilla.components.support.test.mock
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.doAnswer
|
||||
|
||||
class ReviewQualityCheckServiceImplTest {
|
||||
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule()
|
||||
|
||||
@Test
|
||||
fun `GIVEN fetch is called WHEN onResult is invoked THEN product analysis returns the same data`() =
|
||||
runTest {
|
||||
val engineSession = mock<EngineSession>()
|
||||
val expected = mock<ProductAnalysis>()
|
||||
|
||||
doAnswer { invocation ->
|
||||
val onResult: (ProductAnalysis) -> Unit = invocation.getArgument(1)
|
||||
onResult(expected)
|
||||
}.`when`(engineSession).requestProductAnalysis(any(), any(), any())
|
||||
|
||||
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 = ReviewQualityCheckServiceImpl(BrowserStore(browserState))
|
||||
|
||||
val actual = tested.fetchProductReview()
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fetch is called WHEN onException is invoked THEN product analysis returns null`() =
|
||||
runTest {
|
||||
val engineSession = mock<EngineSession>()
|
||||
|
||||
doAnswer { invocation ->
|
||||
val onException: (Throwable) -> Unit = invocation.getArgument(2)
|
||||
onException(RuntimeException())
|
||||
}.`when`(engineSession).requestProductAnalysis(any(), any(), any())
|
||||
|
||||
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 = ReviewQualityCheckServiceImpl(BrowserStore(browserState))
|
||||
|
||||
assertNull(tested.fetchProductReview())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN fetch is called THEN fetch is called for the selected tab`() = runTest {
|
||||
val engineSession = mock<EngineSession>()
|
||||
|
||||
val expected = mock<ProductAnalysis>()
|
||||
doAnswer { invocation ->
|
||||
val onResult: (ProductAnalysis) -> Unit = invocation.getArgument(1)
|
||||
onResult(expected)
|
||||
}.`when`(engineSession).requestProductAnalysis(any(), any(), any())
|
||||
|
||||
val tab1 = createTab(
|
||||
url = "https://www.mozilla.org",
|
||||
id = "1",
|
||||
)
|
||||
val tab2 = createTab(
|
||||
url = "https://www.shopping.org/product",
|
||||
id = "2",
|
||||
engineSession = engineSession,
|
||||
)
|
||||
val browserState = BrowserState(
|
||||
tabs = listOf(tab1, tab2),
|
||||
selectedTabId = tab2.id,
|
||||
)
|
||||
|
||||
val tested = ReviewQualityCheckServiceImpl(BrowserStore(browserState))
|
||||
|
||||
val actual = tested.fetchProductReview()
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue