Skip to content

Commit

Permalink
Merge pull request #2526 from wordpress-mobile/feature/6619-limit-sta…
Browse files Browse the repository at this point in the history
…ts-refresh

Feature/6619 limit stats refresh
  • Loading branch information
JorgeMucientes authored Oct 6, 2022
2 parents 87eadb0 + 680ada3 commit 6459816
Show file tree
Hide file tree
Showing 13 changed files with 1,503 additions and 247 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import org.wordpress.android.fluxc.example.R
import org.wordpress.android.fluxc.example.prependToLog
import org.wordpress.android.fluxc.example.ui.StoreSelectingFragment
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.model.leaderboards.WCTopPerformerProductModel
import org.wordpress.android.fluxc.persistence.entity.TopPerformerProductEntity
import org.wordpress.android.fluxc.store.WCLeaderboardsStore
import org.wordpress.android.fluxc.store.WCStatsStore.StatsGranularity
import org.wordpress.android.fluxc.store.WCStatsStore.StatsGranularity.DAYS
Expand Down Expand Up @@ -82,7 +82,7 @@ class WooLeaderboardsFragment : StoreSelectingFragment() {
coroutineScope.launch {
try {
takeAsyncRequestWithValidSite {
wcLeaderboardsStore.fetchProductLeaderboards(
wcLeaderboardsStore.fetchTopPerformerProducts(
it,
unit,
forceRefresh = false
Expand All @@ -101,7 +101,7 @@ class WooLeaderboardsFragment : StoreSelectingFragment() {
private fun launchProductLeaderboardsCacheRetrieval(unit: StatsGranularity) {
coroutineScope.launch {
try {
takeAsyncRequestWithValidSite { wcLeaderboardsStore.fetchCachedProductLeaderboards(it, unit) }
takeAsyncRequestWithValidSite { wcLeaderboardsStore.fetchTopPerformerProducts(it, unit) }
?.model
?.let { logLeaderboardResponse(it, unit) }
?: prependToLog("Couldn't fetch Products Leaderboards.")
Expand All @@ -111,14 +111,13 @@ class WooLeaderboardsFragment : StoreSelectingFragment() {
}
}

private fun logLeaderboardResponse(model: List<WCTopPerformerProductModel>, unit: StatsGranularity) {
private fun logLeaderboardResponse(model: List<TopPerformerProductEntity>, unit: StatsGranularity) {
model.forEach {
prependToLog(" Top Performer Product id: ${it.product.remoteProductId ?: "Product id not available"}")
prependToLog(" Top Performer Product name: ${it.product.name ?: "Product name not available"}")
prependToLog(" Top Performer Product id: ${it.productId ?: "Product id not available"}")
prependToLog(" Top Performer Product name: ${it.name ?: "Product name not available"}")
prependToLog(" Top Performer currency: ${it.currency ?: "Currency not available"}")
prependToLog(" Top Performer quantity: ${it.quantity ?: "Quantity not available"}")
prependToLog(" Top Performer total: ${it.total ?: "total not available"}")
prependToLog(" Top Performer id: ${it.id ?: "ID not available"}")
prependToLog(" --------- Product ---------")
}
prependToLog("========== Top Performers of the $unit =========")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import com.yarolegovich.wellsql.WellSql
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
Expand All @@ -20,142 +19,223 @@ import org.wordpress.android.fluxc.model.WCProductModel
import org.wordpress.android.fluxc.model.leaderboards.WCProductLeaderboardsMapper
import org.wordpress.android.fluxc.model.leaderboards.WCTopPerformerProductModel
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooPayload
import org.wordpress.android.fluxc.network.rest.wpcom.wc.leaderboards.LeaderboardsApiResponse
import org.wordpress.android.fluxc.network.rest.wpcom.wc.leaderboards.LeaderboardsApiResponse.Type.PRODUCTS
import org.wordpress.android.fluxc.network.rest.wpcom.wc.leaderboards.LeaderboardsRestClient
import org.wordpress.android.fluxc.persistence.WellSqlConfig
import org.wordpress.android.fluxc.persistence.dao.TopPerformerProductsDao
import org.wordpress.android.fluxc.persistence.entity.TopPerformerProductEntity
import org.wordpress.android.fluxc.store.WCLeaderboardsStore
import org.wordpress.android.fluxc.store.WCProductStore
import org.wordpress.android.fluxc.store.WCStatsStore.StatsGranularity.DAYS
import org.wordpress.android.fluxc.test
import org.wordpress.android.fluxc.tools.initCoroutineEngine
import org.wordpress.android.fluxc.wc.leaderboards.WCLeaderboardsTestFixtures.duplicatedTopPerformersList
import org.wordpress.android.fluxc.wc.leaderboards.WCLeaderboardsTestFixtures.generateSampleLeaderboardsApiResponse
import org.wordpress.android.fluxc.wc.leaderboards.WCLeaderboardsTestFixtures.stubSite
import org.wordpress.android.fluxc.wc.leaderboards.WCLeaderboardsTestFixtures.stubbedTopPerformersList

@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner::class)
class WCLeaderboardsStoreTest {
private val restClient: LeaderboardsRestClient = mock()
private val productStore: WCProductStore = mock()
private var mapper: WCProductLeaderboardsMapper = mock()
private val topPerformersDao: TopPerformerProductsDao = mock()

private lateinit var storeUnderTest: WCLeaderboardsStore
private lateinit var restClient: LeaderboardsRestClient
private lateinit var productStore: WCProductStore
private lateinit var mapper: WCProductLeaderboardsMapper

@Before
fun setUp() {
fun setup(prepareMocks: () -> Unit = {}) {
prepareMocks()
createStoreUnderTest()
val appContext = RuntimeEnvironment.application.applicationContext
val config = SingleStoreWellSqlConfigForTests(
appContext,
listOf(SiteModel::class.java, WCTopPerformerProductModel::class.java, WCProductModel::class.java),
WellSqlConfig.ADDON_WOOCOMMERCE
appContext,
listOf(
SiteModel::class.java,
WCTopPerformerProductModel::class.java,
WCProductModel::class.java
),
WellSqlConfig.ADDON_WOOCOMMERCE
)
WellSql.init(config)
config.reset()
initMocks()
createStoreUnderTest()
}

@Test
fun `fetch product leaderboards with empty result should return WooError`() = test {
whenever(restClient.fetchLeaderboards(stubSite, DAYS, null, null, forceRefresh = false))
.thenReturn(WooPayload(emptyArray()))
fun `fetch top performer products with empty result should return WooError`() = test {
setup()
givenFetchLeaderBoardsReturns(emptyArray())

val result = storeUnderTest.fetchTopPerformerProducts(stubSite)

val result = storeUnderTest.fetchProductLeaderboards(stubSite)
assertThat(result.model).isNull()
assertThat(result.error).isNotNull
}

@Test
fun `fetch product leaderboards should filter leaderboards by PRODUCTS type`() = test {
mapper = spy()
createStoreUnderTest()
fun `fetch top performer products should filter leaderboards by PRODUCTS type`() = test {
setup { mapper = spy() }
val response = generateSampleLeaderboardsApiResponse()
val filteredResponse = response?.firstOrNull { it.type == PRODUCTS }
givenFetchLeaderBoardsReturns(response)

whenever(restClient.fetchLeaderboards(stubSite, DAYS, null, null, forceRefresh = false))
.thenReturn(WooPayload(response))
storeUnderTest.fetchTopPerformerProducts(stubSite)

storeUnderTest.fetchProductLeaderboards(stubSite)
verify(mapper).map(filteredResponse!!, stubSite, productStore, DAYS)
verify(mapper).mapTopPerformerProductsEntity(
response?.firstOrNull { it.type == PRODUCTS }!!,
stubSite,
productStore,
DAYS
)
}

@Test
fun `fetch product leaderboards should call mapper once`() = test {
mapper = spy()
createStoreUnderTest()
fun `fetch top performer products should call mapper once`() = test {
setup()
val response = generateSampleLeaderboardsApiResponse()
givenFetchLeaderBoardsReturns(response)

whenever(restClient.fetchLeaderboards(stubSite, DAYS, null, null, forceRefresh = false))
.thenReturn(WooPayload(response))
storeUnderTest.fetchTopPerformerProducts(stubSite)

storeUnderTest.fetchProductLeaderboards(stubSite)
verify(mapper, times(1)).map(any(), any(), any(), any())
verify(mapper, times(1)).mapTopPerformerProductsEntity(any(), any(), any(), any())
}

@Test
fun `fetch product leaderboards should return WooResult correctly`() = test {
val response = generateSampleLeaderboardsApiResponse()
val filteredResponse = response?.firstOrNull { it.type == PRODUCTS }

whenever(restClient.fetchLeaderboards(stubSite, DAYS, null, null, forceRefresh = false))
.thenReturn(WooPayload(response))

whenever(mapper.map(filteredResponse!!, stubSite, productStore, DAYS)).thenReturn(stubbedTopPerformersList)

val result = storeUnderTest.fetchProductLeaderboards(stubSite)
assertThat(result.model).isNotNull
assertThat(result.model).isEqualTo(stubbedTopPerformersList)
assertThat(result.error).isNull()
}
fun `fetch top performer products should return mapped top performer entities correctly`() =
test {
setup()
val response = generateSampleLeaderboardsApiResponse()
givenFetchLeaderBoardsReturns(response)
givenTopPerformersMapperReturns(
givenResponse = response?.firstOrNull { it.type == PRODUCTS }!!,
returnedTopPerformersList = TOP_PERFORMER_ENTITY_LIST
)

val result = storeUnderTest.fetchTopPerformerProducts(stubSite)

assertThat(result.model).isNotNull
assertThat(result.model).isEqualTo(TOP_PERFORMER_ENTITY_LIST)
assertThat(result.error).isNull()
}

@Test
fun `fetch product leaderboards from a invalid site ID should return WooResult with error`() = test {
val response = generateSampleLeaderboardsApiResponse()
val filteredResponse = response?.firstOrNull { it.type == PRODUCTS }
fun `fetch top performer products from a invalid site ID should return WooResult with error`() =
test {
setup()
val response = generateSampleLeaderboardsApiResponse()
givenFetchLeaderBoardsReturns(response)
givenTopPerformersMapperReturns(
givenResponse = response?.firstOrNull { it.type == PRODUCTS }!!,
returnedTopPerformersList = TOP_PERFORMER_ENTITY_LIST,
SiteModel().apply { id = 100 },
)

whenever(restClient.fetchLeaderboards(stubSite, DAYS, null, null, forceRefresh = false))
.thenReturn(WooPayload(response))
val result = storeUnderTest.fetchTopPerformerProducts(stubSite)

whenever(mapper.map(
filteredResponse!!,
SiteModel().apply { id = 100 },
productStore,
DAYS))
.thenReturn(stubbedTopPerformersList)
assertThat(result.model).isNull()
assertThat(result.error).isNotNull
}

val result = storeUnderTest.fetchProductLeaderboards(stubSite)
assertThat(result.model).isNull()
assertThat(result.error).isNotNull
}
@Test
fun `fetching top performer products should update database with new data`() =
test {
setup()
val response = generateSampleLeaderboardsApiResponse()
givenFetchLeaderBoardsReturns(response)
givenTopPerformersMapperReturns(
givenResponse = response?.firstOrNull { it.type == PRODUCTS }!!,
returnedTopPerformersList = TOP_PERFORMER_ENTITY_LIST
)

storeUnderTest.fetchTopPerformerProducts(stubSite)

verify(topPerformersDao, times(1))
.updateTopPerformerProductsFor(
stubSite.siteId,
DAYS.toString(),
TOP_PERFORMER_ENTITY_LIST
)
}

@Test
fun `fetch product leaderboards should distinct duplicate items`() = test {
val response = generateSampleLeaderboardsApiResponse()
val filteredResponse = response?.firstOrNull { it.type == PRODUCTS }
fun `invalidating top performer products should update database`() =
test {
setup()
givenCachedTopPerformers()

storeUnderTest.invalidateTopPerformers(stubSite.siteId)

verify(topPerformersDao, times(1))
.getTopPerformerProductsForSite(stubSite.siteId)
verify(topPerformersDao, times(1))
.updateTopPerformerProductsForSite(
stubSite.siteId,
INVALIDATED_TOP_PERFORMER_ENTITY_LIST
)
}

private suspend fun givenCachedTopPerformers() {
whenever(
topPerformersDao.getTopPerformerProductsForSite(stubSite.siteId)
).thenReturn(TOP_PERFORMER_ENTITY_LIST)
}

private suspend fun givenFetchLeaderBoardsReturns(response: Array<LeaderboardsApiResponse>?) {
whenever(restClient.fetchLeaderboards(stubSite, DAYS, null, null, forceRefresh = false))
.thenReturn(WooPayload(response))

whenever(mapper.map(filteredResponse!!, stubSite, productStore, DAYS)).thenReturn(duplicatedTopPerformersList)
.thenReturn(WooPayload(response))
}

val result = storeUnderTest.fetchProductLeaderboards(stubSite)
assertThat(result.model).isNotNull
assertThat(result.model!!.size).isEqualTo(1)
assertThat(result.model).isNotEqualTo(stubbedTopPerformersList)
assertThat(result.error).isNull()
private fun createStoreUnderTest() {
storeUnderTest = WCLeaderboardsStore(
restClient,
productStore,
mapper,
initCoroutineEngine(),
topPerformersDao
)
}

private fun initMocks() {
restClient = mock()
productStore = mock()
mapper = mock()
private suspend fun givenTopPerformersMapperReturns(
givenResponse: LeaderboardsApiResponse,
returnedTopPerformersList: List<TopPerformerProductEntity>,
siteModel: SiteModel = stubSite
) {
whenever(
mapper.mapTopPerformerProductsEntity(
givenResponse,
siteModel,
productStore,
DAYS
)
).thenReturn(returnedTopPerformersList)
}

private fun createStoreUnderTest() =
WCLeaderboardsStore(
restClient,
productStore,
mapper,
initCoroutineEngine()
).apply { storeUnderTest = this }
companion object {
val TOP_PERFORMER_ENTITY_LIST =
listOf(
TopPerformerProductEntity(
siteId = 1,
granularity = "Today",
productId = 123,
name = "product",
imageUrl = null,
quantity = 5,
currency = "USD",
total = 10.5,
millisSinceLastUpdated = 100
)
)
val INVALIDATED_TOP_PERFORMER_ENTITY_LIST =
listOf(
TopPerformerProductEntity(
siteId = 1,
granularity = "Today",
productId = 123,
name = "product",
imageUrl = null,
quantity = 5,
currency = "USD",
total = 10.5,
millisSinceLastUpdated = 0
)
)
}
}
Loading

0 comments on commit 6459816

Please sign in to comment.