diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosGetProductById.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosGetProductById.kt index 2f20ae9caa31..1aac5df4076a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosGetProductById.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosGetProductById.kt @@ -1,8 +1,8 @@ package com.woocommerce.android.ui.woopos.common.data -import com.woocommerce.android.model.Product -import com.woocommerce.android.model.toAppModel import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.woopos.common.data.models.WCProductToWooPosProductModelMapper +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2 import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.withContext import org.wordpress.android.fluxc.network.rest.wpcom.wc.product.ProductRestClient @@ -12,8 +12,9 @@ class WooPosGetProductById @Inject constructor( private val selectedSite: SelectedSite, private val cache: WooPosProductsCache, private val productRestClient: ProductRestClient, + private val productMapper: WCProductToWooPosProductModelMapper, ) { - suspend operator fun invoke(productId: Long): Product? = withContext(IO) { + suspend operator fun invoke(productId: Long): WooPosProductModelVersion2? = withContext(IO) { val cachedProduct = cache.getProductById(productId) if (cachedProduct != null) { return@withContext cachedProduct @@ -27,7 +28,7 @@ class WooPosGetProductById @Inject constructor( return@withContext if (!remoteProductResult.isError) { val remoteProduct = remoteProductResult.productWithMetaData.product - val product = remoteProduct.toAppModel() + val product = productMapper.map(remoteProduct) cache.addAll(listOf(product)) product } else { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosPopularProductsProvider.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosPopularProductsProvider.kt index af9529af0998..9fbda8e758ab 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosPopularProductsProvider.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosPopularProductsProvider.kt @@ -1,8 +1,8 @@ package com.woocommerce.android.ui.woopos.common.data -import com.woocommerce.android.model.Product -import com.woocommerce.android.model.toAppModel import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.woopos.common.data.models.WCProductToWooPosProductModelMapper +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2 import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.wordpress.android.fluxc.store.WCProductStore @@ -16,15 +16,16 @@ class WooPosPopularProductsProvider @Inject constructor( private val productStore: WCProductStore, private val productsCache: WooPosProductsCache, private val productsTypesFilterConfig: WooPosProductsTypesFilterConfig, + private val productMapper: WCProductToWooPosProductModelMapper, ) { companion object { private const val MAX_POPULAR_PRODUCTS = 10 } private val mutex = Mutex() - private val popularProductsCache = mutableListOf() + private val popularProductsCache = mutableListOf() - suspend fun getPopularProducts(): List = mutex.withLock { popularProductsCache } + suspend fun getPopularProducts(): List = mutex.withLock { popularProductsCache } suspend fun addPopularItemsToCache() = mutex.withLock { productsCache.addAll(popularProductsCache) @@ -44,7 +45,7 @@ class WooPosPopularProductsProvider @Inject constructor( Result.failure(Exception(result.error.message)) } else { val products = result.model ?: emptyList() - var productsAppModel = products.map { it.toAppModel() } + val productsAppModel = products.map { productMapper.map(it) } popularProductsCache.clear() popularProductsCache.addAll(productsAppModel) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsCache.kt index 727d3bb5ed24..a9171a34d2f3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsCache.kt @@ -1,21 +1,21 @@ package com.woocommerce.android.ui.woopos.common.data -import com.woocommerce.android.model.Product +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2 interface WooPosProductsCache { companion object { const val MAX_CACHE_SIZE = 2_000 } - suspend fun addAll(products: List) + suspend fun addAll(products: List) - suspend fun getAll(): List + suspend fun getAll(): List - suspend fun getProductById(productId: Long): Product? + suspend fun getProductById(productId: Long): WooPosProductModelVersion2? - suspend fun getProductByGlobalUniqueIdentifier(globalUniqueIdentifier: String): Product? + suspend fun getProductByGlobalUniqueIdentifier(globalUniqueIdentifier: String): WooPosProductModelVersion2? - suspend fun updateProduct(product: Product) + suspend fun updateProduct(product: WooPosProductModelVersion2) suspend fun deleteProduct(productId: Long) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsInMemoryCache.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsInMemoryCache.kt index 72e87164e16f..ef5f95fd0d56 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsInMemoryCache.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsInMemoryCache.kt @@ -1,7 +1,7 @@ package com.woocommerce.android.ui.woopos.common.data -import com.woocommerce.android.model.Product import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache.Companion.MAX_CACHE_SIZE +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2 import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import javax.inject.Inject @@ -16,13 +16,13 @@ class WooPosProductsInMemoryCache @Inject constructor() : WooPosProductsCache { private const val LOAD_FACTOR = 0.75f } - private val productsCache = LinkedHashMap(INITIAL_CAPACITY, LOAD_FACTOR, true) + private val productsCache = LinkedHashMap(INITIAL_CAPACITY, LOAD_FACTOR, true) - override suspend fun addAll(products: List) = mutex.withLock { + override suspend fun addAll(products: List) = mutex.withLock { addAllInternal(products) } - override suspend fun updateProduct(product: Product) = mutex.withLock { + override suspend fun updateProduct(product: WooPosProductModelVersion2) = mutex.withLock { productsCache[product.remoteId] = product } @@ -32,23 +32,22 @@ class WooPosProductsInMemoryCache @Inject constructor() : WooPosProductsCache { } } - override suspend fun getAll(): List = mutex.withLock { + override suspend fun getAll(): List = mutex.withLock { return productsCache.values.toList() } - override suspend fun getProductById(productId: Long): Product? = mutex.withLock { + override suspend fun getProductById(productId: Long): WooPosProductModelVersion2? = mutex.withLock { return productsCache[productId] } - override suspend fun getProductByGlobalUniqueIdentifier(globalUniqueIdentifier: String): Product? { - return productsCache.values.find { it.globalUniqueId == globalUniqueIdentifier } - } + override suspend fun getProductByGlobalUniqueIdentifier(globalUniqueIdentifier: String) = + productsCache.values.find { it.globalUniqueId == globalUniqueIdentifier } override suspend fun clear() = mutex.withLock { productsCache.clear() } - private fun addAllInternal(products: List) { + private fun addAllInternal(products: List) { products.forEach { product -> productsCache[product.remoteId] = product if (productsCache.size > MAX_CACHE_SIZE) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsDataSource.kt index 3cd1e2b19dbf..ce643a19a0f3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsDataSource.kt @@ -1,11 +1,11 @@ package com.woocommerce.android.ui.woopos.home.items.products import com.woocommerce.android.WooException -import com.woocommerce.android.model.Product -import com.woocommerce.android.model.toAppModel import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsTypesFilterConfig +import com.woocommerce.android.ui.woopos.common.data.models.WCProductToWooPosProductModelMapper +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2 import com.woocommerce.android.util.WooLog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -29,7 +29,8 @@ class WooPosProductsDataSource @Inject constructor( private val selectedSite: SelectedSite, private val productsCache: WooPosProductsCache, private val productsIndex: WooPosProductsIndex, - private val productsTypesFilterConfig: WooPosProductsTypesFilterConfig + private val productsTypesFilterConfig: WooPosProductsTypesFilterConfig, + private val posProductMapper: WCProductToWooPosProductModelMapper, ) { private val canLoadMore = AtomicBoolean(false) private val offset = AtomicInteger(0) @@ -57,7 +58,8 @@ class WooPosProductsDataSource @Inject constructor( val pageOneResult = pageOne.await() val pageTwoResult = pageTwo.await() - fun List?.toAppModels(): List = this?.map { it.toAppModel() } ?: emptyList() + fun List?.toAppModels(): List = + this?.map { posProductMapper.map(it) } ?: emptyList() when { pageOneResult.isError -> { @@ -96,7 +98,7 @@ class WooPosProductsDataSource @Inject constructor( } }.flowOn(Dispatchers.IO).take(2) - suspend fun loadMore(): Result> = withContext(Dispatchers.IO) { + suspend fun loadMore(): Result> = withContext(Dispatchers.IO) { if (!canLoadMore.get()) { return@withContext Result.success(productsIndex.getProductList()) } @@ -110,11 +112,11 @@ class WooPosProductsDataSource @Inject constructor( } } - private fun sortProducts(products: List): List { + private fun sortProducts(products: List): List { return products.sortedBy { it.name.lowercase() } } - private suspend fun fetchProducts(): Result> { + private suspend fun fetchProducts(): Result> { val result = fetchProductsFromStore( offset = offset.get(), pageSize = NORMAL_PAGE_SIZE @@ -122,7 +124,7 @@ class WooPosProductsDataSource @Inject constructor( return if (!result.isError) { val productsList = result.model ?: emptyList() - val products = productsList.map { it.toAppModel() } + val products = productsList.map { posProductMapper.map(it) } canLoadMore.set(productsList.size == NORMAL_PAGE_SIZE) offset.addAndGet(NORMAL_PAGE_SIZE) @@ -155,8 +157,8 @@ class WooPosProductsDataSource @Inject constructor( } sealed class ProductsResult { - data class Cached(val products: List) : ProductsResult() - data class Remote(val productsResult: Result>) : ProductsResult() + data class Cached(val products: List) : ProductsResult() + data class Remote(val productsResult: Result>) : ProductsResult() } companion object { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsIndex.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsIndex.kt index e904c7e0b9c1..1fd5571566eb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsIndex.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsIndex.kt @@ -1,7 +1,7 @@ package com.woocommerce.android.ui.woopos.home.items.products -import com.woocommerce.android.model.Product import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2 import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import javax.inject.Inject @@ -19,7 +19,7 @@ class WooPosProductsIndex @Inject constructor( productListIds = (productListIds + productIds).distinct() } - suspend fun getProductList(): List = mutex.withLock { + suspend fun getProductList(): List = mutex.withLock { return productListIds.mapNotNull { productsCache.getProductById(it) } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosPopularProductsProviderTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosPopularProductsProviderTest.kt index 17013151ebe9..641b0c8aaeab 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosPopularProductsProviderTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosPopularProductsProviderTest.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.woopos.common.data import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.woopos.common.data.models.WCProductToWooPosProductModelMapper import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -29,6 +30,7 @@ class WooPosPopularProductsProviderTest { on { filters }.thenReturn(emptyMap()) on { includeTypes }.thenReturn(emptyList()) } + private val productMapper: WCProductToWooPosProductModelMapper = mock() private val sampleProducts = listOf( WCProductModel().copy( @@ -196,6 +198,8 @@ class WooPosPopularProductsProviderTest { selectedSite = selectedSite, productStore = productStore, productsCache = productsCache, - productsTypesFilterConfig = productsTypesFilterConfig + productsTypesFilterConfig = productsTypesFilterConfig, + productMapper = productMapper, + ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsInMemoryCacheTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsInMemoryCacheTest.kt index 14b498f5b5a1..dbe0755ae3fb 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsInMemoryCacheTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/common/data/WooPosProductsInMemoryCacheTest.kt @@ -1,6 +1,6 @@ package com.woocommerce.android.ui.woopos.common.data -import com.woocommerce.android.model.Product +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -8,7 +8,6 @@ import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test -import org.mockito.kotlin.mock import java.math.BigDecimal import java.util.Date @@ -147,10 +146,19 @@ class WooPosProductsInMemoryCacheTest { private fun createTestProduct( id: Long, name: String = "Test Product $id" - ): Product = mock { - on { remoteId }.thenReturn(id) - on { this.name }.thenReturn(name) - on { price }.thenReturn(BigDecimal.TEN) - on { dateCreated }.thenReturn(Date()) - } + ): WooPosProductModelVersion2 = + WooPosProductModelVersion2( + remoteId = id, + parentId = null, + name = name, + sku = "SKU-$id", + pricing = WooPosProductModelVersion2.WooPosPricing.RegularPricing(price = BigDecimal.TEN), + globalUniqueId = "global-$id", + type = WooPosProductModelVersion2.WooPosProductType.SIMPLE, + status = WooPosProductModelVersion2.WooPosProductStatus.PUBLISH, + description = "Description for $name", + shortDescription = "Short description for $name", + isDownloadable = false, + lastModified = "2020-01-01T00:00:00Z", + ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosProductsDataSourceTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosProductsDataSourceTest.kt index 0b62d160a01d..756ab75b1cf8 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosProductsDataSourceTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosProductsDataSourceTest.kt @@ -1,11 +1,15 @@ package com.woocommerce.android.ui.woopos.home.items import com.woocommerce.android.WooException -import com.woocommerce.android.model.Product import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.products.ProductTestUtils import com.woocommerce.android.ui.woopos.common.data.WooPosProductsCache import com.woocommerce.android.ui.woopos.common.data.WooPosProductsTypesFilterConfig +import com.woocommerce.android.ui.woopos.common.data.models.WCProductToWooPosProductModelMapper +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2 +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2.WooPosPricing +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2.WooPosProductImage +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2.WooPosProductStatus +import com.woocommerce.android.ui.woopos.common.data.models.WooPosProductModelVersion2.WooPosProductType import com.woocommerce.android.ui.woopos.home.items.products.WooPosProductsDataSource import com.woocommerce.android.ui.woopos.home.items.products.WooPosProductsIndex import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule @@ -39,44 +43,47 @@ class WooPosProductsDataSourceTest { val coroutinesTestRule = WooPosCoroutineTestRule() private val sampleProducts = listOf( - ProductTestUtils.generateProduct( + generateProduct( productId = 1, productName = "Product 1", amount = "10.0", - productType = "simple", + productType = WooPosProductType.SIMPLE, isDownloadable = false, ), - ProductTestUtils.generateProduct( + generateProduct( productId = 2, productName = "Product 2", amount = "20.0", - productType = "simple", + productType = WooPosProductType.SIMPLE, isDownloadable = false, - ).copy(firstImageUrl = "https://test.com"), - ProductTestUtils.generateProduct( + images = listOf(WooPosProductImage(id = 1, url = "https://test.com", name = "", alt = "")), + ), + generateProduct( productId = 3, productName = "Product 3", amount = "20.0", - productType = "simple", + productType = WooPosProductType.SIMPLE, isDownloadable = false, - ).copy(firstImageUrl = "https://test.com") + images = listOf(WooPosProductImage(id = 1, url = "https://test.com", name = "", alt = "")), + ) ) private val additionalProducts = listOf( - ProductTestUtils.generateProduct( + generateProduct( productId = 4, productName = "Product 4", amount = "10.0", - productType = "simple", + productType = WooPosProductType.SIMPLE, isDownloadable = false, ), - ProductTestUtils.generateProduct( + generateProduct( productId = 5, productName = "Product 5", amount = "20.0", - productType = "simple", + productType = WooPosProductType.SIMPLE, isDownloadable = false, - ).copy(firstImageUrl = "https://test.com"), + images = listOf(WooPosProductImage(id = 1, url = "https://test.com", name = "", alt = "")), + ) ) private val productStore: WCProductStore = mock() @@ -89,6 +96,7 @@ class WooPosProductsDataSourceTest { } private val productsIndex: WooPosProductsIndex = mock() private val productsTypesFilterConfig = WooPosProductsTypesFilterConfig() + private val productMapper: WCProductToWooPosProductModelMapper = mock() @Test fun `given cached products, when loadProducts called, then should emit cached products first`() = runTest { @@ -110,7 +118,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) // WHEN @@ -123,34 +132,36 @@ class WooPosProductsDataSourceTest { } @Test - fun `given cached products, when loadProducts called with forceRefresh, then should not emit cached products`() = runTest { - // GIVEN - whenever(productsCache.getAll()).thenReturn(sampleProducts) - whenever( - productStore.fetchProducts( - site = eq(siteModel), - offset = any(), - pageSize = any(), - sortType = any(), - filterOptions = any(), - includeTypes = any() + fun `given cached products, when loadProducts called with forceRefresh, then should not emit cached products`() = + runTest { + // GIVEN + whenever(productsCache.getAll()).thenReturn(sampleProducts) + whenever( + productStore.fetchProducts( + site = eq(siteModel), + offset = any(), + pageSize = any(), + sortType = any(), + filterOptions = any(), + includeTypes = any() + ) + ).thenReturn(WooResult(listOf())) + whenever(productsIndex.getProductList()).thenReturn(sampleProducts) + val sut = WooPosProductsDataSource( + productStore, + selectedSite, + productsCache, + productsIndex, + productsTypesFilterConfig, + productMapper, ) - ).thenReturn(WooResult(listOf())) - whenever(productsIndex.getProductList()).thenReturn(sampleProducts) - val sut = WooPosProductsDataSource( - productStore, - selectedSite, - productsCache, - productsIndex, - productsTypesFilterConfig - ) - // WHEN - val result = sut.loadProducts(forceRefreshProducts = true).first() + // WHEN + val result = sut.loadProducts(forceRefreshProducts = true).first() - // THEN - assertThat(result).isInstanceOf(WooPosProductsDataSource.ProductsResult.Remote::class.java) - } + // THEN + assertThat(result).isInstanceOf(WooPosProductsDataSource.ProductsResult.Remote::class.java) + } @Test fun `given no products in list cache, when loadProducts called, then should return empty list`() = runTest { @@ -172,7 +183,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) // WHEN @@ -225,7 +237,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) // WHEN @@ -269,7 +282,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) // WHEN @@ -313,7 +327,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) sut.loadProducts(forceRefreshProducts = true).first() @@ -332,11 +347,11 @@ class WooPosProductsDataSourceTest { whenever(productsIndex.getProductList()) .thenReturn( List(25) { - ProductTestUtils.generateProduct( + generateProduct( productId = it.toLong(), productName = "Product $it", amount = "0", - productType = "simple", + productType = WooPosProductType.SIMPLE, isDownloadable = false, ) } @@ -372,7 +387,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) sut.loadProducts(forceRefreshProducts = true).first() @@ -413,7 +429,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) // WHEN @@ -448,7 +465,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) // WHEN @@ -468,21 +486,12 @@ class WooPosProductsDataSourceTest { @Test fun `when loading products, they should be sorted by name in ascending order`() = runTest { // GIVEN - val mockProductC = mock() - whenever(mockProductC.name).thenReturn("C Product") - whenever(mockProductC.remoteId).thenReturn(3L) - - val mockProductA = mock() - whenever(mockProductA.name).thenReturn("A Product") - whenever(mockProductA.remoteId).thenReturn(1L) + val mockProductC = generateProduct(productName = "C Product", productId = 3L) - val mockProductB = mock() - whenever(mockProductB.name).thenReturn("B Product") - whenever(mockProductB.remoteId).thenReturn(2L) + val mockProductA = generateProduct(productName = "A Product", productId = 1L) - val mockProductab = mock() - whenever(mockProductab.name).thenReturn("ab Product") - whenever(mockProductab.remoteId).thenReturn(2L) + val mockProductB = generateProduct(productName = "B Product", productId = 2L) + val mockProductab = generateProduct(productName = "ab Product", productId = 2L) val customUnsortedProducts = listOf(mockProductC, mockProductA, mockProductB, mockProductab) val sortedProducts = listOf(mockProductA, mockProductB, mockProductC) @@ -505,7 +514,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) // WHEN @@ -522,118 +532,122 @@ class WooPosProductsDataSourceTest { } @Test - fun `given successful fetch on both pages, when prepopulateProductsCache called, then add all products`() = runTest { - // GIVEN - val firstPageProducts = List(100) { - WCProductModel().copy( - remoteId = RemoteId(it.toLong()), - attributes = "[]", - status = "draft" - ) - } + fun `given successful fetch on both pages, when prepopulateProductsCache called, then add all products`() = + runTest { + // GIVEN + val firstPageProducts = List(100) { + WCProductModel().copy( + remoteId = RemoteId(it.toLong()), + attributes = "[]", + status = "draft" + ) + } - val secondPageProducts = List(100) { - WCProductModel().copy( - remoteId = RemoteId((it + 100).toLong()), - attributes = "[]", - status = "draft" - ) - } + val secondPageProducts = List(100) { + WCProductModel().copy( + remoteId = RemoteId((it + 100).toLong()), + attributes = "[]", + status = "draft" + ) + } - whenever( - productStore.fetchProducts( - site = eq(siteModel), - offset = eq(0), - pageSize = eq(100), - sortType = any(), - filterOptions = any(), - includeTypes = any() - ) - ).thenReturn(WooResult(firstPageProducts)) + whenever( + productStore.fetchProducts( + site = eq(siteModel), + offset = eq(0), + pageSize = eq(100), + sortType = any(), + filterOptions = any(), + includeTypes = any() + ) + ).thenReturn(WooResult(firstPageProducts)) - whenever( - productStore.fetchProducts( - site = eq(siteModel), - offset = eq(100), - pageSize = eq(100), - sortType = any(), - filterOptions = any(), - includeTypes = any() - ) - ).thenReturn(WooResult(secondPageProducts)) + whenever( + productStore.fetchProducts( + site = eq(siteModel), + offset = eq(100), + pageSize = eq(100), + sortType = any(), + filterOptions = any(), + includeTypes = any() + ) + ).thenReturn(WooResult(secondPageProducts)) - val sut = WooPosProductsDataSource( - productStore, - selectedSite, - productsCache, - productsIndex, - productsTypesFilterConfig - ) + val sut = WooPosProductsDataSource( + productStore, + selectedSite, + productsCache, + productsIndex, + productsTypesFilterConfig, + productMapper, + ) - // WHEN - val result = sut.prepopulateProductsCache() + // WHEN + val result = sut.prepopulateProductsCache() - // THEN - verify(productsCache).clear() - verify(productsCache).addAll(any()) - assertThat(result.isSuccess).isTrue() - } + // THEN + verify(productsCache).clear() + verify(productsCache).addAll(any()) + assertThat(result.isSuccess).isTrue() + } @Test - fun `given first fetch success but second fetch fails, when prepopulateProductsCache called, then should add first page products`() = runTest { - // GIVEN - val firstPageProducts = List(100) { - WCProductModel().copy( - remoteId = RemoteId(it.toLong()), - attributes = "[]", - status = "draft" + fun `given first fetch success but second fetch fails, when prepopulateProductsCache called, then should add first page products`() = + runTest { + // GIVEN + val firstPageProducts = List(100) { + WCProductModel().copy( + remoteId = RemoteId(it.toLong()), + attributes = "[]", + status = "draft" + ) + } + + val wooError = WooError( + WooErrorType.GENERIC_ERROR, + GenericErrorType.UNKNOWN, + "Failed to fetch products on second page" ) - } - val wooError = WooError( - WooErrorType.GENERIC_ERROR, - GenericErrorType.UNKNOWN, - "Failed to fetch products on second page" - ) + whenever( + productStore.fetchProducts( + site = eq(siteModel), + offset = eq(0), + pageSize = eq(100), + sortType = any(), + filterOptions = any(), + includeTypes = any() + ) + ).thenReturn(WooResult(firstPageProducts)) - whenever( - productStore.fetchProducts( - site = eq(siteModel), - offset = eq(0), - pageSize = eq(100), - sortType = any(), - filterOptions = any(), - includeTypes = any() - ) - ).thenReturn(WooResult(firstPageProducts)) + whenever( + productStore.fetchProducts( + site = eq(siteModel), + offset = eq(100), + pageSize = eq(100), + sortType = any(), + filterOptions = any(), + includeTypes = any() + ) + ).thenReturn(WooResult(wooError)) - whenever( - productStore.fetchProducts( - site = eq(siteModel), - offset = eq(100), - pageSize = eq(100), - sortType = any(), - filterOptions = any(), - includeTypes = any() + val sut = WooPosProductsDataSource( + productStore, + selectedSite, + productsCache, + productsIndex, + productsTypesFilterConfig, + productMapper, ) - ).thenReturn(WooResult(wooError)) - val sut = WooPosProductsDataSource( - productStore, - selectedSite, - productsCache, - productsIndex, - productsTypesFilterConfig - ) - - // WHEN - val result = sut.prepopulateProductsCache() + // WHEN + val result = sut.prepopulateProductsCache() - // THEN - verify(productsCache).clear() - verify(productsCache).addAll(any()) - assertThat(result.isSuccess).isTrue() - } + // THEN + verify(productsCache).clear() + verify(productsCache).addAll(any()) + assertThat(result.isSuccess).isTrue() + } @Test fun `given first fetch fails, when prepopulateProductsCache called, then should return failure`() = runTest { @@ -660,7 +674,8 @@ class WooPosProductsDataSourceTest { selectedSite, productsCache, productsIndex, - productsTypesFilterConfig + productsTypesFilterConfig, + productMapper, ) // WHEN @@ -674,68 +689,93 @@ class WooPosProductsDataSourceTest { } @Test - fun `given both pages return products, when prepopulateProductsCache called, then should add all products to cache`() = runTest { - // GIVEN - val firstPageProducts = List(100) { - WCProductModel().copy( - remoteId = RemoteId(it.toLong()), - attributes = "[]", - status = "draft" - ) - } + fun `given both pages return products, when prepopulateProductsCache called, then should add all products to cache`() = + runTest { + // GIVEN + val firstPageProducts = List(100) { + WCProductModel().copy( + remoteId = RemoteId(it.toLong()), + attributes = "[]", + status = "draft" + ) + } - val secondPageProducts = List(50) { - WCProductModel().copy( - remoteId = RemoteId((it + 100).toLong()), - attributes = "[]", - status = "draft" - ) - } + val secondPageProducts = List(50) { + WCProductModel().copy( + remoteId = RemoteId((it + 100).toLong()), + attributes = "[]", + status = "draft" + ) + } - whenever( - productStore.fetchProducts( - site = eq(siteModel), - offset = eq(0), - pageSize = eq(100), - sortType = any(), - filterOptions = any(), - includeTypes = any() + whenever( + productStore.fetchProducts( + site = eq(siteModel), + offset = eq(0), + pageSize = eq(100), + sortType = any(), + filterOptions = any(), + includeTypes = any() + ) + ).thenReturn(WooResult(firstPageProducts)) + + whenever( + productStore.fetchProducts( + site = eq(siteModel), + offset = eq(100), + pageSize = eq(100), + sortType = any(), + filterOptions = any(), + includeTypes = any() + ) + ).thenReturn(WooResult(secondPageProducts)) + + val sut = WooPosProductsDataSource( + productStore, + selectedSite, + productsCache, + productsIndex, + productsTypesFilterConfig, + productMapper, ) - ).thenReturn(WooResult(firstPageProducts)) - whenever( - productStore.fetchProducts( - site = eq(siteModel), - offset = eq(100), - pageSize = eq(100), + // WHEN + val result = sut.prepopulateProductsCache() + + // THEN + verify(productsCache).clear() + verify(productsCache).addAll(any()) + assertThat(result.isSuccess).isTrue() + verify(productStore, times(2)).fetchProducts( + site = any(), + offset = any(), + pageSize = any(), sortType = any(), filterOptions = any(), includeTypes = any() ) - ).thenReturn(WooResult(secondPageProducts)) - - val sut = WooPosProductsDataSource( - productStore, - selectedSite, - productsCache, - productsIndex, - productsTypesFilterConfig - ) - - // WHEN - val result = sut.prepopulateProductsCache() + } - // THEN - verify(productsCache).clear() - verify(productsCache).addAll(any()) - assertThat(result.isSuccess).isTrue() - verify(productStore, times(2)).fetchProducts( - site = any(), - offset = any(), - pageSize = any(), - sortType = any(), - filterOptions = any(), - includeTypes = any() - ) - } + private fun generateProduct( + productId: Long = 1, + productName: String = "Product 1", + amount: String = "10.0", + productType: WooPosProductType = WooPosProductType.SIMPLE, + isDownloadable: Boolean = false, + images: List = emptyList() + ) = WooPosProductModelVersion2( + remoteId = productId, + name = productName, + pricing = WooPosPricing.RegularPricing(amount.toBigDecimal()), + type = productType, + isDownloadable = isDownloadable, + parentId = null, + sku = "", + globalUniqueId = "", + status = WooPosProductStatus.PUBLISH, + description = "", + shortDescription = "", + lastModified = "", + images = images, + ) }