Updated on 2026-08-14
This commit is contained in:
parent
7832fae9f3
commit
e2026ec410
5 changed files with 527 additions and 30 deletions
|
|
@ -121,8 +121,8 @@ internal class DefaultNFTRuntimeStore(
|
|||
},
|
||||
)
|
||||
}
|
||||
val assetsCount = when (assets) {
|
||||
is NFTCollection.Assets.Value -> assets.items.size
|
||||
val assetsCount = when {
|
||||
assets is NFTCollection.Assets.Value && assets.items.isNotEmpty() -> assets.items.size
|
||||
else -> data.count
|
||||
}
|
||||
data.copy(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.datasource.local.nft
|
|||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
|
|
@ -58,6 +59,7 @@ class NFTPersistenceStoreFactory @Inject constructor(
|
|||
types = types,
|
||||
defaultValue = defaultValue,
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler { defaultValue },
|
||||
produceFile = { context.dataStoreFile(fileName = fileName) },
|
||||
scope = appScope,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
package com.tangem.datasource.local.nft
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.nft.models.NFTSalePrice
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigInteger
|
||||
|
||||
class DefaultNFTRuntimeStoreTest {
|
||||
|
||||
private val network = createNetwork()
|
||||
|
||||
private val store = DefaultNFTRuntimeStore(
|
||||
network = network,
|
||||
collectionsRuntimeStore = RuntimeSharedStore(),
|
||||
pricesRuntimeStore = RuntimeSharedStore(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN collection with empty loaded assets WHEN getCollections THEN collection is kept`() = runTest {
|
||||
// Arrange
|
||||
val collection = createCollection(
|
||||
count = 1,
|
||||
assets = NFTCollection.Assets.Value(items = emptyList(), source = StatusSource.ACTUAL),
|
||||
)
|
||||
store.initialize(collections = createCollections(collection), prices = emptyMap())
|
||||
|
||||
// Act
|
||||
val content = store.getCollections().first().content
|
||||
|
||||
// Assert
|
||||
assertThat(content).isInstanceOf(NFTCollections.Content.Collections::class.java)
|
||||
content as NFTCollections.Content.Collections
|
||||
val actual = content.collections.orEmpty().single()
|
||||
assertThat(actual.count).isEqualTo(1)
|
||||
assertThat(actual.assets).isInstanceOf(NFTCollection.Assets.Value::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN collection with loaded assets WHEN getCollections THEN count recalculated from assets`() = runTest {
|
||||
// Arrange
|
||||
val collection = createCollection(
|
||||
count = 5,
|
||||
assets = NFTCollection.Assets.Value(items = listOf(createAsset()), source = StatusSource.ACTUAL),
|
||||
)
|
||||
store.initialize(collections = createCollections(collection), prices = emptyMap())
|
||||
|
||||
// Act
|
||||
val content = store.getCollections().first().content
|
||||
|
||||
// Assert
|
||||
content as NFTCollections.Content.Collections
|
||||
assertThat(content.collections.orEmpty().single().count).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN collection with zero count and not loaded assets WHEN getCollections THEN collection filtered out`() =
|
||||
runTest {
|
||||
// Arrange
|
||||
val collection = createCollection(count = 0, assets = NFTCollection.Assets.Empty)
|
||||
store.initialize(collections = createCollections(collection), prices = emptyMap())
|
||||
|
||||
// Act
|
||||
val content = store.getCollections().first().content
|
||||
|
||||
// Assert
|
||||
content as NFTCollections.Content.Collections
|
||||
assertThat(content.collections.orEmpty()).isEmpty()
|
||||
}
|
||||
|
||||
private fun createCollections(vararg collections: NFTCollection) = NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = collections.toList(),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
)
|
||||
|
||||
private fun createCollection(count: Int, assets: NFTCollection.Assets) = NFTCollection(
|
||||
id = NFTCollection.Identifier.EVM(tokenAddress = TOKEN_ADDRESS),
|
||||
network = network,
|
||||
name = "Test collection",
|
||||
description = null,
|
||||
logoUrl = null,
|
||||
count = count,
|
||||
assets = assets,
|
||||
)
|
||||
|
||||
private fun createAsset(): NFTAsset {
|
||||
val assetId = NFTAsset.Identifier.EVM(
|
||||
tokenAddress = TOKEN_ADDRESS,
|
||||
tokenId = BigInteger.ONE,
|
||||
contractType = NFTAsset.Identifier.EVM.ContractType.ERC721,
|
||||
)
|
||||
return NFTAsset(
|
||||
id = assetId,
|
||||
collectionId = NFTCollection.Identifier.EVM(tokenAddress = TOKEN_ADDRESS),
|
||||
network = network,
|
||||
contractType = "ERC721",
|
||||
owner = null,
|
||||
name = "Test asset",
|
||||
description = null,
|
||||
amount = null,
|
||||
decimals = 0,
|
||||
salePrice = NFTSalePrice.Empty(assetId),
|
||||
rarity = null,
|
||||
media = null,
|
||||
traits = emptyList(),
|
||||
source = StatusSource.ACTUAL,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNetwork() = Network(
|
||||
id = Network.ID(rawId = Network.RawID("ethereum"), derivationPath = Network.DerivationPath.None),
|
||||
name = "Ethereum",
|
||||
currencySymbol = "ETH",
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.ERC20,
|
||||
hasFiatFeeRate = true,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val TOKEN_ADDRESS = "0x0000000000000000000000000000000000000001"
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.datasource.local.nft.NFTPersistenceStore
|
|||
import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStore
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStoreFactory
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetIdentifierConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetSalePriceConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionConverter
|
||||
|
|
@ -181,29 +182,27 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
getNFTPersistenceStore(userWalletId, network)
|
||||
.getCollectionsSync()
|
||||
?.map { collection ->
|
||||
if (collection.identifier == sdkCollectionId) {
|
||||
collection.copy(assets = assets)
|
||||
} else {
|
||||
collection
|
||||
}
|
||||
}
|
||||
?.let { collections ->
|
||||
saveCollectionsInRuntime(
|
||||
saveAssetsInRuntime(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collections = collections,
|
||||
collectionId = collectionId,
|
||||
assets = assets,
|
||||
)
|
||||
saveCollectionsInPersistence(
|
||||
|
||||
// local cache failures must not affect the runtime state which is already up to date
|
||||
runSuspendCatching {
|
||||
updateAssetsInPersistence(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collections = collections,
|
||||
sdkCollectionId = sdkCollectionId,
|
||||
assets = assets,
|
||||
)
|
||||
}.onFailure { error ->
|
||||
TangemLogger.e("Failed to persist NFT assets for $network", error)
|
||||
}
|
||||
}.onLeft { throwable ->
|
||||
if (throwable !is UnsupportedOperationException) {
|
||||
TangemLogger.e("Failed to refresh NFT assets for $network", throwable)
|
||||
saveFailedStateInRuntime(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
|
|
@ -269,18 +268,29 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
expireCollections(userWalletId, network)
|
||||
|
||||
val collections = walletManagersFacade.getNFTCollections(userWalletId, network)
|
||||
val mergedCollections = collections.mergeWithStoredAssets(userWalletId, network)
|
||||
|
||||
// local cache failures must not affect successfully fetched collections
|
||||
val mergedCollections = runSuspendCatching {
|
||||
collections.mergeWithStoredAssets(userWalletId, network)
|
||||
}.getOrElse { error ->
|
||||
TangemLogger.e("Failed to merge NFT collections with stored assets for $network", error)
|
||||
collections
|
||||
}
|
||||
|
||||
saveCollectionsInRuntime(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
runSuspendCatching {
|
||||
saveCollectionsInPersistence(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
}.onFailure { error ->
|
||||
TangemLogger.e("Failed to persist NFT collections for $network", error)
|
||||
}
|
||||
|
||||
if (refreshAssets) {
|
||||
mergedCollections.forEach { collection ->
|
||||
|
|
@ -292,6 +302,7 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
}.onLeft { throwable ->
|
||||
TangemLogger.e("Failed to refresh NFT collections for $network", throwable)
|
||||
saveFailedStateInRuntime(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
|
|
@ -391,12 +402,72 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun saveAssetsInRuntime(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collectionId: NFTCollection.Identifier,
|
||||
assets: List<SdkNFTAsset>,
|
||||
) {
|
||||
val store = getNFTRuntimeStore(userWalletId, network)
|
||||
val storedCollections = store.getCollectionsSync()
|
||||
val content = storedCollections.content as? NFTCollections.Content.Collections ?: return
|
||||
|
||||
val convertedAssets = assets
|
||||
.map { asset -> NFTSdkAssetConverter.convert(network to asset) }
|
||||
.filter { it.id !is NFTAsset.Identifier.Unknown }
|
||||
|
||||
val updatedCollections = content.collections
|
||||
?.map { collection ->
|
||||
if (collection.id == collectionId) {
|
||||
collection.copy(
|
||||
assets = NFTCollection.Assets.Value(
|
||||
items = convertedAssets,
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
collection
|
||||
}
|
||||
}
|
||||
|
||||
store.saveCollections(
|
||||
storedCollections.copy(content = content.copy(collections = updatedCollections)),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateAssetsInPersistence(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
sdkCollectionId: SdkNFTCollection.Identifier,
|
||||
assets: List<SdkNFTAsset>,
|
||||
) {
|
||||
val storedCollections = getNFTPersistenceStore(userWalletId, network).getCollectionsSync() ?: return
|
||||
val updatedCollections = storedCollections.map { collection ->
|
||||
if (collection.identifier == sdkCollectionId) {
|
||||
collection.copy(assets = assets)
|
||||
} else {
|
||||
collection
|
||||
}
|
||||
}
|
||||
saveCollectionsInPersistence(userWalletId, network, updatedCollections)
|
||||
}
|
||||
|
||||
private suspend fun saveCollectionsInPersistence(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collections: List<SdkNFTCollection>,
|
||||
) {
|
||||
getNFTPersistenceStore(userWalletId, network).saveCollections(collections)
|
||||
val serializableCollections = collections
|
||||
.filter { it.identifier !is SdkNFTCollection.Identifier.Unknown }
|
||||
.map { collection ->
|
||||
collection.copy(
|
||||
assets = collection.assets.filter { asset ->
|
||||
asset.identifier !is SdkNFTAsset.Identifier.Unknown &&
|
||||
asset.collectionIdentifier !is SdkNFTCollection.Identifier.Unknown
|
||||
},
|
||||
)
|
||||
}
|
||||
getNFTPersistenceStore(userWalletId, network).saveCollections(serializableCollections)
|
||||
}
|
||||
|
||||
private suspend fun saveSalePriceInRuntime(userWalletId: UserWalletId, network: Network, salePrice: NFTSalePrice) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,288 @@
|
|||
package com.tangem.data.nft
|
||||
|
||||
import android.content.Context
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.datasource.local.nft.NFTPersistenceStore
|
||||
import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStore
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStoreFactory
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionIdentifierConverter
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.nft.models.NFTSalePrice
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.io.IOException
|
||||
import java.math.BigInteger
|
||||
import com.tangem.blockchain.nft.models.NFTAsset as SdkNFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultNFTRepositoryTest {
|
||||
|
||||
private val nftPersistenceStoreFactory: NFTPersistenceStoreFactory = mockk()
|
||||
private val nftRuntimeStoreFactory: NFTRuntimeStoreFactory = mockk()
|
||||
private val walletManagersFacade: WalletManagersFacade = mockk()
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk()
|
||||
private val context: Context = mockk()
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val userWallet = mockk<UserWallet.Hot> {
|
||||
every { walletId } returns userWalletId
|
||||
}
|
||||
|
||||
private val network: Network = MockCryptoCurrencyFactory().ethereum.network
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(nftPersistenceStoreFactory, nftRuntimeStoreFactory, walletManagersFacade, userWalletsListRepository)
|
||||
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(userWallet))
|
||||
every { context.resources } returns mockk()
|
||||
}
|
||||
|
||||
private fun createRepository() = DefaultNFTRepository(
|
||||
nftPersistenceStoreFactory = nftPersistenceStoreFactory,
|
||||
nftRuntimeStoreFactory = nftRuntimeStoreFactory,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
networkFactory = mockk(),
|
||||
excludedBlockchains = ExcludedBlockchains(),
|
||||
context = context,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN collections fetched WHEN persistence write fails THEN runtime keeps actual data`() = runTest {
|
||||
// Arrange
|
||||
val runtimeStore = FakeNFTRuntimeStore(network)
|
||||
val persistenceStore = mockk<NFTPersistenceStore> {
|
||||
coEvery { getCollectionsSync() } returns null
|
||||
coEvery { getSalePricesSync() } returns null
|
||||
coEvery { saveCollections(any()) } throws IOException("Failed to write to disk")
|
||||
}
|
||||
every { nftPersistenceStoreFactory.provide(userWalletId, network) } returns persistenceStore
|
||||
every { nftRuntimeStoreFactory.provide(network) } returns runtimeStore
|
||||
coEvery { walletManagersFacade.getNFTCollections(userWalletId, network) } returns listOf(createSdkCollection())
|
||||
|
||||
// Act
|
||||
createRepository().refreshCollections(userWalletId, listOf(network))
|
||||
|
||||
// Assert
|
||||
val content = runtimeStore.getCollectionsSync().content
|
||||
assertThat(content).isInstanceOf(NFTCollections.Content.Collections::class.java)
|
||||
content as NFTCollections.Content.Collections
|
||||
assertThat(content.source).isEqualTo(StatusSource.ACTUAL)
|
||||
assertThat(content.collections).hasSize(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN runtime has collection missing in persistence WHEN refreshAssets THEN assets saved to runtime`() =
|
||||
runTest {
|
||||
// Arrange
|
||||
val sdkCollection = createSdkCollection()
|
||||
val collectionId = NFTSdkCollectionIdentifierConverter.convert(sdkCollection.identifier)
|
||||
val runtimeStore = FakeNFTRuntimeStore(network)
|
||||
val persistenceStore = mockk<NFTPersistenceStore> {
|
||||
coEvery { getCollectionsSync() } returns null
|
||||
coEvery { getSalePricesSync() } returns null
|
||||
coEvery { saveCollections(any()) } returns Unit
|
||||
}
|
||||
every { nftPersistenceStoreFactory.provide(userWalletId, network) } returns persistenceStore
|
||||
every { nftRuntimeStoreFactory.provide(network) } returns runtimeStore
|
||||
coEvery {
|
||||
walletManagersFacade.getNFTCollections(userWalletId, network)
|
||||
} returns listOf(sdkCollection)
|
||||
coEvery {
|
||||
walletManagersFacade.getNFTAssets(userWalletId, network, sdkCollection.identifier)
|
||||
} returns listOf(createSdkAsset())
|
||||
coEvery { walletManagersFacade.getNFTSalePrice(userWalletId, network, any(), any()) } returns null
|
||||
|
||||
val repository = createRepository()
|
||||
// seed runtime store with the fetched collection, persistence stays empty
|
||||
repository.refreshCollections(userWalletId, listOf(network))
|
||||
|
||||
// Act
|
||||
repository.refreshAssets(userWalletId, network, collectionId)
|
||||
|
||||
// Assert
|
||||
val content = runtimeStore.getCollectionsSync().content
|
||||
assertThat(content).isInstanceOf(NFTCollections.Content.Collections::class.java)
|
||||
content as NFTCollections.Content.Collections
|
||||
val assets = content.collections.orEmpty().single().assets
|
||||
assertThat(assets).isInstanceOf(NFTCollection.Assets.Value::class.java)
|
||||
assets as NFTCollection.Assets.Value
|
||||
assertThat(assets.items).hasSize(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN fetch returns no assets WHEN refreshAssets THEN empty loaded value saved to runtime`() = runTest {
|
||||
// Arrange
|
||||
val sdkCollection = createSdkCollection()
|
||||
val collectionId = NFTSdkCollectionIdentifierConverter.convert(sdkCollection.identifier)
|
||||
val runtimeStore = FakeNFTRuntimeStore(network)
|
||||
val persistenceStore = mockk<NFTPersistenceStore> {
|
||||
coEvery { getCollectionsSync() } returns null
|
||||
coEvery { getSalePricesSync() } returns null
|
||||
coEvery { saveCollections(any()) } returns Unit
|
||||
}
|
||||
every { nftPersistenceStoreFactory.provide(userWalletId, network) } returns persistenceStore
|
||||
every { nftRuntimeStoreFactory.provide(network) } returns runtimeStore
|
||||
coEvery {
|
||||
walletManagersFacade.getNFTCollections(userWalletId, network)
|
||||
} returns listOf(sdkCollection)
|
||||
coEvery {
|
||||
walletManagersFacade.getNFTAssets(userWalletId, network, sdkCollection.identifier)
|
||||
} returns emptyList()
|
||||
|
||||
val repository = createRepository()
|
||||
repository.refreshCollections(userWalletId, listOf(network))
|
||||
|
||||
// Act
|
||||
repository.refreshAssets(userWalletId, network, collectionId)
|
||||
|
||||
// Assert
|
||||
val content = runtimeStore.getCollectionsSync().content
|
||||
assertThat(content).isInstanceOf(NFTCollections.Content.Collections::class.java)
|
||||
content as NFTCollections.Content.Collections
|
||||
val assets = content.collections.orEmpty().single().assets
|
||||
assertThat(assets).isInstanceOf(NFTCollection.Assets.Value::class.java)
|
||||
assets as NFTCollection.Assets.Value
|
||||
assertThat(assets.items).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no cached collections WHEN fetch fails THEN error state saved to runtime`() = runTest {
|
||||
// Arrange
|
||||
val runtimeStore = FakeNFTRuntimeStore(network)
|
||||
val persistenceStore = mockk<NFTPersistenceStore> {
|
||||
coEvery { getCollectionsSync() } returns null
|
||||
coEvery { getSalePricesSync() } returns null
|
||||
}
|
||||
every { nftPersistenceStoreFactory.provide(userWalletId, network) } returns persistenceStore
|
||||
every { nftRuntimeStoreFactory.provide(network) } returns runtimeStore
|
||||
coEvery { walletManagersFacade.getNFTCollections(userWalletId, network) } throws IOException("HTTP 500")
|
||||
|
||||
// Act
|
||||
createRepository().refreshCollections(userWalletId, listOf(network))
|
||||
|
||||
// Assert
|
||||
val content = runtimeStore.getCollectionsSync().content
|
||||
assertThat(content).isInstanceOf(NFTCollections.Content.Error::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cached collections WHEN fetch fails THEN cache marked as only cache`() = runTest {
|
||||
// Arrange
|
||||
val runtimeStore = FakeNFTRuntimeStore(network)
|
||||
val persistenceStore = mockk<NFTPersistenceStore> {
|
||||
coEvery { getCollectionsSync() } returns listOf(createSdkCollection())
|
||||
coEvery { getSalePricesSync() } returns null
|
||||
}
|
||||
every { nftPersistenceStoreFactory.provide(userWalletId, network) } returns persistenceStore
|
||||
every { nftRuntimeStoreFactory.provide(network) } returns runtimeStore
|
||||
coEvery { walletManagersFacade.getNFTCollections(userWalletId, network) } throws IOException("HTTP 500")
|
||||
|
||||
// Act
|
||||
createRepository().refreshCollections(userWalletId, listOf(network))
|
||||
|
||||
// Assert
|
||||
val content = runtimeStore.getCollectionsSync().content
|
||||
assertThat(content).isInstanceOf(NFTCollections.Content.Collections::class.java)
|
||||
content as NFTCollections.Content.Collections
|
||||
assertThat(content.source).isEqualTo(StatusSource.ONLY_CACHE)
|
||||
assertThat(content.collections).hasSize(1)
|
||||
}
|
||||
|
||||
private fun createSdkCollection(assets: List<SdkNFTAsset> = emptyList()) = SdkNFTCollection(
|
||||
identifier = SdkNFTCollection.Identifier.EVM(tokenAddress = TOKEN_ADDRESS),
|
||||
blockchainId = Blockchain.Ethereum.id,
|
||||
name = "Test collection",
|
||||
description = null,
|
||||
logoUrl = null,
|
||||
count = 1,
|
||||
assets = assets,
|
||||
)
|
||||
|
||||
private fun createSdkAsset() = SdkNFTAsset(
|
||||
identifier = SdkNFTAsset.Identifier.EVM(
|
||||
tokenId = BigInteger.ONE,
|
||||
tokenAddress = TOKEN_ADDRESS,
|
||||
contractType = SdkNFTAsset.Identifier.EVM.ContractType.ERC721,
|
||||
),
|
||||
collectionIdentifier = SdkNFTCollection.Identifier.EVM(tokenAddress = TOKEN_ADDRESS),
|
||||
blockchainId = Blockchain.Ethereum.id,
|
||||
contractType = "ERC721",
|
||||
owner = null,
|
||||
name = "Test asset",
|
||||
description = null,
|
||||
amount = BigInteger.ONE,
|
||||
decimals = 0,
|
||||
salePrice = null,
|
||||
rarity = null,
|
||||
media = null,
|
||||
traits = emptyList(),
|
||||
)
|
||||
|
||||
private class FakeNFTRuntimeStore(private val network: Network) : NFTRuntimeStore {
|
||||
|
||||
private var collections: NFTCollections = NFTCollections.empty(network)
|
||||
private var prices: Map<NFTAsset.Identifier, NFTSalePrice> = emptyMap()
|
||||
|
||||
override suspend fun initialize(collections: NFTCollections, prices: Map<NFTAsset.Identifier, NFTSalePrice>) {
|
||||
this.collections = collections
|
||||
this.prices = prices
|
||||
}
|
||||
|
||||
override fun getCollections(): Flow<NFTCollections> = flowOf(collections)
|
||||
|
||||
override suspend fun getCollectionsSync(): NFTCollections = collections
|
||||
|
||||
override fun getAsset(
|
||||
collectionId: NFTCollection.Identifier,
|
||||
assetId: NFTAsset.Identifier,
|
||||
): Flow<NFTAsset?> = flowOf(null)
|
||||
|
||||
override fun getSalePrice(assetId: NFTAsset.Identifier): Flow<NFTSalePrice> =
|
||||
flowOf(prices[assetId] ?: NFTSalePrice.Empty(assetId))
|
||||
|
||||
override suspend fun getSalePriceSync(assetId: NFTAsset.Identifier): NFTSalePrice =
|
||||
prices[assetId] ?: NFTSalePrice.Empty(assetId)
|
||||
|
||||
override suspend fun saveCollections(collections: NFTCollections) {
|
||||
this.collections = collections
|
||||
}
|
||||
|
||||
override suspend fun saveSalePrice(salePrice: NFTSalePrice) {
|
||||
prices = prices + (salePrice.assetId to salePrice)
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
collections = NFTCollections.empty(network)
|
||||
prices = emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TOKEN_ADDRESS = "0x0000000000000000000000000000000000000001"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue