Updated on 2026-08-14
This commit is contained in:
commit
c50840894f
739 changed files with 27664 additions and 4701 deletions
|
|
@ -85,6 +85,18 @@ interface TangemPayApi {
|
|||
@Body body: FreezeUnfreezeCardRequest,
|
||||
): ApiResponse<FreezeUnfreezeCardResponse>
|
||||
|
||||
@GET("v1/fees/{type}")
|
||||
suspend fun getFee(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Path("type") type: String,
|
||||
): ApiResponse<FeeResponse>
|
||||
|
||||
@POST("v1/customer/card/reissue")
|
||||
suspend fun reissueCard(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ReissueCardRequest,
|
||||
): ApiResponse<ReissueCardResponse>
|
||||
|
||||
@POST("v1/customer/card/withdraw/data")
|
||||
suspend fun getWithdrawData(
|
||||
@Header("Authorization") authHeader: String,
|
||||
|
|
@ -96,4 +108,10 @@ interface TangemPayApi {
|
|||
@Header("Authorization") authHeader: String,
|
||||
@Body body: WithdrawRequest,
|
||||
): ApiResponse<WithdrawResponse>
|
||||
|
||||
@PATCH("v1/card")
|
||||
suspend fun updateCardDisplayName(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: UpdateCardDisplayNameRequest,
|
||||
): ApiResponse<UpdateCardDisplayNameResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReissueCardRequest(
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UpdateCardDisplayNameRequest(
|
||||
@Json(name = "display_name") val displayName: String,
|
||||
)
|
||||
|
|
@ -29,6 +29,7 @@ data class CustomerMeResponse(
|
|||
@Json(name = "status") val status: Status,
|
||||
@Json(name = "updated_at") val updatedAt: String,
|
||||
@Json(name = "payment_account_id") val paymentAccountId: String,
|
||||
@Json(name = "display_name") val displayName: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Status {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class FeeResponse(
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "type") val type: String,
|
||||
@Json(name = "amount") val amount: String,
|
||||
@Json(name = "currency") val currency: String,
|
||||
@Json(name = "description") val description: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReissueCardResponse(
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "status") val status: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UpdateCardDisplayNameResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "display_name") val displayName: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -42,7 +42,8 @@ data class UserTokensResponse(
|
|||
return otherToken.contractAddress == this.contractAddress &&
|
||||
otherToken.networkId == this.networkId &&
|
||||
otherToken.derivationPath == this.derivationPath &&
|
||||
otherToken.decimals == this.decimals
|
||||
otherToken.decimals == this.decimals &&
|
||||
otherToken.dynamicAddressesEnabled == this.dynamicAddressesEnabled
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = calculateHashCode(
|
||||
|
|
@ -50,6 +51,7 @@ data class UserTokensResponse(
|
|||
networkId.hashCode(),
|
||||
derivationPath.hashCode(),
|
||||
decimals.hashCode(),
|
||||
dynamicAddressesEnabled.hashCode(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package com.tangem.datasource.di
|
|||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayReissueCardStore
|
||||
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.TangemPayReissueCardStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -20,4 +22,12 @@ internal object TangemPayStoresModule {
|
|||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemPayReissueCardStore(): TangemPayReissueCardStore {
|
||||
return DefaultTangemPayReissueCardStore(
|
||||
feeStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,11 +29,12 @@ sealed interface NetworkStatusDM {
|
|||
/**
|
||||
* Verified
|
||||
*
|
||||
* @property networkId network id
|
||||
* @property derivationPath derivation path
|
||||
* @property selectedAddress selected address
|
||||
* @property availableAddresses available addresses
|
||||
* @property amounts amounts
|
||||
* @property networkId network id
|
||||
* @property derivationPath derivation path
|
||||
* @property selectedAddress selected address
|
||||
* @property availableAddresses available addresses
|
||||
* @property amounts amounts
|
||||
* @property yieldSupplyStatuses yield supply statuses
|
||||
*/
|
||||
@NameLabel("amounts")
|
||||
data class Verified(
|
||||
|
|
@ -65,6 +66,11 @@ sealed interface NetworkStatusDM {
|
|||
@Json(name = "error_message") val errorMessage: String,
|
||||
) : NetworkStatusDM
|
||||
|
||||
/**
|
||||
* Id
|
||||
*
|
||||
* @property value blockchain id [com.tangem.blockchain.common.Blockchain.id]
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ID(
|
||||
@Json(name = "value") val value: String,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PR
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY
|
||||
import com.tangem.datasource.local.preferences.utils.CleanupKeyMigration
|
||||
import com.tangem.datasource.local.preferences.utils.SharedPreferencesKeyMigration
|
||||
import com.tangem.datasource.local.preferences.utils.SwapCurrencyIdMigration
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
||||
|
|
@ -80,6 +81,7 @@ internal object PreferencesDataStore {
|
|||
legacyKeyName = LEGACY_DEFAULT_KEY_NAME,
|
||||
keyName = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY.name,
|
||||
),
|
||||
SwapCurrencyIdMigration(),
|
||||
CleanupKeyMigration(key = APP_LOGS_KEY),
|
||||
CleanupKeyMigration(key = IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY),
|
||||
CleanupKeyMigration(key = IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY),
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ object PreferencesKeys {
|
|||
|
||||
val HAS_HAD_FIRST_TOP_UP_KEY by lazy { stringPreferencesKey(name = "hasHadFirstTopUp") }
|
||||
|
||||
val PENDING_DISCOVERY_SYNC_KEY by lazy { stringPreferencesKey(name = "pendingDiscoverySync") }
|
||||
val PENDING_ASSETS_DISCOVERY_KEY by lazy { stringPreferencesKey(name = "pendingAssetsDiscovery") }
|
||||
|
||||
// region Notifications
|
||||
val NOTIFICATIONS_APPLICATION_ID_KEY by lazy { stringPreferencesKey(name = "notificationsApplicationId") }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
package com.tangem.datasource.local.preferences.utils
|
||||
|
||||
import androidx.datastore.core.DataMigration
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
|
||||
/**
|
||||
* Migrates cached CryptoCurrency.ID strings from old blockchain.id format to new networkId format.
|
||||
*
|
||||
* After refactoring, Network.rawId stores backendId values (e.g. "ethereum") instead of
|
||||
* blockchain.id values (e.g. "ETH"). CryptoCurrency.ID body contains this value, so cached IDs
|
||||
* like "coin⟨ETH⟩ethereum" must become "coin⟨ethereum⟩ethereum".
|
||||
*
|
||||
* Affected DataStore keys: [PreferencesKeys.SWAP_TRANSACTIONS_KEY],
|
||||
* [PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY].
|
||||
*/
|
||||
internal class SwapCurrencyIdMigration : DataMigration<Preferences> {
|
||||
|
||||
override suspend fun shouldMigrate(currentData: Preferences): Boolean {
|
||||
return currentData.contains(PreferencesKeys.SWAP_TRANSACTIONS_KEY) ||
|
||||
currentData.contains(PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY)
|
||||
}
|
||||
|
||||
override suspend fun migrate(currentData: Preferences): Preferences {
|
||||
val mutablePrefs = currentData.toMutablePreferences()
|
||||
|
||||
currentData[PreferencesKeys.SWAP_TRANSACTIONS_KEY]?.let { json ->
|
||||
mutablePrefs[PreferencesKeys.SWAP_TRANSACTIONS_KEY] = migrateJson(json)
|
||||
}
|
||||
|
||||
currentData[PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY]?.let { json ->
|
||||
mutablePrefs[PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY] = migrateJson(json)
|
||||
}
|
||||
|
||||
return mutablePrefs.toPreferences()
|
||||
}
|
||||
|
||||
override suspend fun cleanUp() {
|
||||
// nothing to clean up
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces old blockchain.id values with networkId values inside CryptoCurrency.ID strings
|
||||
* found anywhere in the JSON. Works by finding all `⟨oldId⟩` and `⟨oldId→` patterns and
|
||||
* replacing the old ID with the new one.
|
||||
*/
|
||||
private fun migrateJson(json: String): String {
|
||||
var result = json
|
||||
|
||||
for ((oldId, newId) in BLOCKCHAIN_ID_TO_NETWORK_ID) {
|
||||
// Body without derivation path: ⟨oldId⟩ → ⟨newId⟩
|
||||
result = result.replace("$BODY_START$oldId$BODY_END", "$BODY_START$newId$BODY_END")
|
||||
// Body with derivation path: ⟨oldId→ → ⟨newId→
|
||||
result = result.replace(
|
||||
"$BODY_START$oldId$DERIVATION_DELIMITER",
|
||||
"$BODY_START$newId$DERIVATION_DELIMITER",
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val BODY_START = '\u27E8' // ⟨
|
||||
const val BODY_END = '\u27E9' // ⟩
|
||||
const val DERIVATION_DELIMITER = '\u2192' // →
|
||||
|
||||
/** Mapping of old blockchain.id → new networkId (only entries where values differ). */
|
||||
val BLOCKCHAIN_ID_TO_NETWORK_ID = mapOf(
|
||||
"ARBITRUM-ONE" to "arbitrum-one",
|
||||
"ARBITRUM/test" to "arbitrum-one/test",
|
||||
"AVALANCHE" to "avalanche",
|
||||
"AVALANCHE/test" to "avalanche/test",
|
||||
"BINANCE" to "binancecoin",
|
||||
"BINANCE/test" to "binancecoin/test",
|
||||
"BSC" to "binance-smart-chain",
|
||||
"BSC/test" to "binance-smart-chain/test",
|
||||
"BTC" to "bitcoin",
|
||||
"BTC/test" to "bitcoin/test",
|
||||
"BCH" to "bitcoin-cash",
|
||||
"BCH/test" to "bitcoin-cash/test",
|
||||
"CARDANO-S" to "cardano",
|
||||
"DOGE" to "dogecoin",
|
||||
"DUC" to "ducatus",
|
||||
"ETH" to "ethereum",
|
||||
"ETH/test" to "ethereum/test",
|
||||
"ETC" to "ethereum-classic",
|
||||
"ETC/test" to "ethereum-classic/test",
|
||||
"ETH-Pow" to "ethereum-pow-iou",
|
||||
"ETH-Pow/test" to "ethereum-pow-iou/test",
|
||||
"FTM" to "fantom",
|
||||
"FTM/test" to "fantom/test",
|
||||
"GNO" to "xdai",
|
||||
"KAS" to "kaspa",
|
||||
"KAS/test" to "kaspa/test",
|
||||
"KAVA" to "kava",
|
||||
"KAVA/test" to "kava/test",
|
||||
"Kusama" to "kusama",
|
||||
"LTC" to "litecoin",
|
||||
"NEAR" to "near-protocol",
|
||||
"NEAR/test" to "near-protocol/test",
|
||||
"NEXA" to "nexa",
|
||||
"NEXA/test" to "nexa/test",
|
||||
"OPTIMISM" to "optimistic-ethereum",
|
||||
"Polkadot" to "polkadot",
|
||||
"POLYGON" to "polygon-pos",
|
||||
"POLYGON/test" to "polygon-pos/test",
|
||||
"RSK" to "rootstock",
|
||||
"SOLANA" to "solana",
|
||||
"SOLANA/test" to "solana/test",
|
||||
"TELOS" to "telos",
|
||||
"TELOS/test" to "telos/test",
|
||||
"The-Open-Network" to "the-open-network",
|
||||
"The-Open-Network/test" to "the-open-network/test",
|
||||
"TRON" to "tron",
|
||||
"TRON/test" to "tron/test",
|
||||
"XLM" to "stellar",
|
||||
"XLM/test" to "stellar/test",
|
||||
"XRP" to "xrp",
|
||||
"XTZ" to "tezos",
|
||||
"DASH" to "dash",
|
||||
"xdc" to "xdc-network",
|
||||
"xdc/test" to "xdc-network/test",
|
||||
"hedera" to "hedera-hashgraph",
|
||||
"hedera/test" to "hedera-hashgraph/test",
|
||||
"areon" to "areon-network",
|
||||
"areon/test" to "areon-network/test",
|
||||
"pls" to "pulsechain",
|
||||
"pls/test" to "pulsechain/test",
|
||||
"zkSyncEra" to "zksync",
|
||||
"zkSyncEra/test" to "zksync/test",
|
||||
"polygonZkEVM" to "polygon-zkevm",
|
||||
"polygonZkEVM/test" to "polygon-zkevm/test",
|
||||
"flare" to "flare-network",
|
||||
"flare/test" to "flare-network/test",
|
||||
"playa3ull" to "playa3ull-games",
|
||||
"sei" to "sei-network",
|
||||
"sei/test" to "sei-network/test",
|
||||
"casper" to "casper-network",
|
||||
"casper/test" to "casper-network/test",
|
||||
"odyssey" to "dione",
|
||||
"odyssey/test" to "dione/test",
|
||||
"hyperliquid" to "hyperevm",
|
||||
"hyperliquid/test" to "hyperevm/test",
|
||||
"quai" to "quai-network",
|
||||
"quai/test" to "quai-network/test",
|
||||
"manta/test" to "manta-pacific/test",
|
||||
"dischain" to "ethereumfair",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.domain.models.TangemPayReissueCardFee
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
internal class DefaultTangemPayReissueCardStore(
|
||||
private val feeStore: RuntimeDataStore<TangemPayReissueCardFee>,
|
||||
) : TangemPayReissueCardStore {
|
||||
|
||||
override suspend fun storeReissueFee(
|
||||
userWalletId: UserWalletId,
|
||||
tangemPayReissueCardFee: TangemPayReissueCardFee,
|
||||
) {
|
||||
feeStore.store(userWalletId.stringValue, tangemPayReissueCardFee)
|
||||
}
|
||||
|
||||
override suspend fun getReissueFee(userWalletId: UserWalletId): TangemPayReissueCardFee? {
|
||||
return feeStore.getSyncOrNull(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
override suspend fun storeReissueOrderId(cardId: String, orderId: String) {
|
||||
// TODO v_rodionov: #[REDACTED_TASK_KEY] store orderId in app prefs
|
||||
}
|
||||
|
||||
override suspend fun getOrderId(cardId: String): String? {
|
||||
// TODO v_rodionov: #[REDACTED_TASK_KEY] store orderId in app prefs
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.domain.models.TangemPayReissueCardFee
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface TangemPayReissueCardStore {
|
||||
|
||||
suspend fun storeReissueFee(userWalletId: UserWalletId, tangemPayReissueCardFee: TangemPayReissueCardFee)
|
||||
|
||||
suspend fun getReissueFee(userWalletId: UserWalletId): TangemPayReissueCardFee?
|
||||
|
||||
suspend fun storeReissueOrderId(cardId: String, orderId: String)
|
||||
|
||||
suspend fun getOrderId(cardId: String): String?
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ sealed interface PaymentAccountStatusValueDM {
|
|||
@Json(name = "is_pin_set") val isPinSet: Boolean,
|
||||
@Json(name = "fiat_balance") val fiatBalance: FiatBalanceDM,
|
||||
@Json(name = "crypto_balance") val cryptoBalance: CryptoBalanceDM,
|
||||
@Json(name = "display_name") val displayName: String?,
|
||||
) : PaymentAccountStatusValueDM
|
||||
|
||||
@NameLabel("card_issue_failed")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,224 @@
|
|||
package com.tangem.datasource.local.preferences.utils
|
||||
|
||||
import androidx.datastore.preferences.core.mutablePreferencesOf
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class SwapCurrencyIdMigrationTest {
|
||||
|
||||
private val migration = SwapCurrencyIdMigration()
|
||||
|
||||
// region shouldMigrate
|
||||
|
||||
@Test
|
||||
fun `shouldMigrate returns true when swap transactions key exists`() = runTest {
|
||||
val prefs = mutablePreferencesOf(PreferencesKeys.SWAP_TRANSACTIONS_KEY to "[]")
|
||||
|
||||
assertThat(migration.shouldMigrate(prefs)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shouldMigrate returns true when last swapped currency key exists`() = runTest {
|
||||
val prefs = mutablePreferencesOf(PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY to "[]")
|
||||
|
||||
assertThat(migration.shouldMigrate(prefs)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shouldMigrate returns true when both keys exist`() = runTest {
|
||||
val prefs = mutablePreferencesOf(
|
||||
PreferencesKeys.SWAP_TRANSACTIONS_KEY to "[]",
|
||||
PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY to "[]",
|
||||
)
|
||||
|
||||
assertThat(migration.shouldMigrate(prefs)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shouldMigrate returns false when no keys exist`() = runTest {
|
||||
val prefs = mutablePreferencesOf()
|
||||
|
||||
assertThat(migration.shouldMigrate(prefs)).isFalse()
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region migrate — coin IDs without derivation path
|
||||
|
||||
@Test
|
||||
fun `migrates simple coin ID - ETH to ethereum`() = runTest {
|
||||
val result = migrateLastSwapped(currencyIdJson("coin${BS}ETH${BE}ethereum"))
|
||||
|
||||
assertThat(result).isEqualTo(currencyIdJson("coin${BS}ethereum${BE}ethereum"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrates simple coin ID - BTC to bitcoin`() = runTest {
|
||||
val result = migrateLastSwapped(currencyIdJson("coin${BS}BTC${BE}bitcoin"))
|
||||
|
||||
assertThat(result).isEqualTo(currencyIdJson("coin${BS}bitcoin${BE}bitcoin"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrates BSC to binance-smart-chain`() = runTest {
|
||||
val result = migrateLastSwapped(currencyIdJson("coin${BS}BSC${BE}binancecoin"))
|
||||
|
||||
assertThat(result).isEqualTo(currencyIdJson("coin${BS}binance-smart-chain${BE}binancecoin"))
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region migrate — coin IDs with derivation path
|
||||
|
||||
@Test
|
||||
fun `migrates coin ID with derivation path`() = runTest {
|
||||
val result = migrateLastSwapped(currencyIdJson("coin${BS}ETH${DP}12367123${BE}ethereum"))
|
||||
|
||||
assertThat(result).isEqualTo(currencyIdJson("coin${BS}ethereum${DP}12367123${BE}ethereum"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrates POLYGON with derivation path`() = runTest {
|
||||
val result = migrateLastSwapped(currencyIdJson("coin${BS}POLYGON${DP}99999${BE}polygon-pos"))
|
||||
|
||||
assertThat(result).isEqualTo(currencyIdJson("coin${BS}polygon-pos${DP}99999${BE}polygon-pos"))
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region migrate — token IDs
|
||||
|
||||
@Test
|
||||
fun `migrates token ID with contract address`() = runTest {
|
||||
val result = migrateLastSwapped(currencyIdJson("token${BS}ETH${BE}usdt${CA}0xdAC17"))
|
||||
|
||||
assertThat(result).isEqualTo(currencyIdJson("token${BS}ethereum${BE}usdt${CA}0xdAC17"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrates token ID with derivation path and contract address`() = runTest {
|
||||
val result = migrateLastSwapped(
|
||||
currencyIdJson("token${BS}ETH${DP}12345${BE}usdt${CA}0xdAC17"),
|
||||
)
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
currencyIdJson("token${BS}ethereum${DP}12345${BE}usdt${CA}0xdAC17"),
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region migrate — swap transactions (both from and to IDs)
|
||||
|
||||
@Test
|
||||
fun `migrates both fromCryptoCurrencyId and toCryptoCurrencyId`() = runTest {
|
||||
val from = "coin${BS}ETH${BE}ethereum"
|
||||
val to = "coin${BS}BTC${BE}bitcoin"
|
||||
val oldJson = "[{\"fromCryptoCurrencyId\":\"$from\",\"toCryptoCurrencyId\":\"$to\"}]"
|
||||
|
||||
val expectedFrom = "coin${BS}ethereum${BE}ethereum"
|
||||
val expectedTo = "coin${BS}bitcoin${BE}bitcoin"
|
||||
val expected = "[{\"fromCryptoCurrencyId\":\"$expectedFrom\",\"toCryptoCurrencyId\":\"$expectedTo\"}]"
|
||||
|
||||
val result = migrateSwapTransactions(oldJson)
|
||||
|
||||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region migrate — no-op cases
|
||||
|
||||
@Test
|
||||
fun `does not modify already migrated IDs`() = runTest {
|
||||
val json = currencyIdJson("coin${BS}ethereum${BE}ethereum")
|
||||
|
||||
val result = migrateLastSwapped(json)
|
||||
|
||||
assertThat(result).isEqualTo(json)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not modify IDs where blockchain id equals networkId`() = runTest {
|
||||
val json = currencyIdJson("coin${BS}cosmos${BE}cosmos")
|
||||
|
||||
val result = migrateLastSwapped(json)
|
||||
|
||||
assertThat(result).isEqualTo(json)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not modify empty list`() = runTest {
|
||||
val result = migrateLastSwapped("[]")
|
||||
|
||||
assertThat(result).isEqualTo("[]")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region migrate — multiple entries
|
||||
|
||||
@Test
|
||||
fun `migrates multiple entries in list`() = runTest {
|
||||
val old1 = currencyIdValue("coin${BS}ETH${BE}ethereum")
|
||||
val old2 = currencyIdValue("coin${BS}TRON${BE}tron")
|
||||
val oldJson = "[$old1,$old2]"
|
||||
|
||||
val new1 = currencyIdValue("coin${BS}ethereum${BE}ethereum")
|
||||
val new2 = currencyIdValue("coin${BS}tron${BE}tron")
|
||||
val expected = "[$new1,$new2]"
|
||||
|
||||
val result = migrateLastSwapped(oldJson)
|
||||
|
||||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region migrate — preserves unrelated keys
|
||||
|
||||
@Test
|
||||
fun `preserves unrelated preference keys`() = runTest {
|
||||
val unrelatedKey = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY
|
||||
val unrelatedValue = "some_value"
|
||||
val prefs = mutablePreferencesOf(
|
||||
PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY to currencyIdJson("coin${BS}ETH${BE}ethereum"),
|
||||
unrelatedKey to unrelatedValue,
|
||||
)
|
||||
|
||||
val result = migration.migrate(prefs)
|
||||
|
||||
assertThat(result[unrelatedKey]).isEqualTo(unrelatedValue)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region helpers
|
||||
|
||||
private suspend fun migrateLastSwapped(json: String): String? {
|
||||
val prefs = mutablePreferencesOf(PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY to json)
|
||||
val result = migration.migrate(prefs)
|
||||
return result[PreferencesKeys.LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY]
|
||||
}
|
||||
|
||||
private suspend fun migrateSwapTransactions(json: String): String? {
|
||||
val prefs = mutablePreferencesOf(PreferencesKeys.SWAP_TRANSACTIONS_KEY to json)
|
||||
val result = migration.migrate(prefs)
|
||||
return result[PreferencesKeys.SWAP_TRANSACTIONS_KEY]
|
||||
}
|
||||
|
||||
private fun currencyIdJson(id: String): String = "[${currencyIdValue(id)}]"
|
||||
|
||||
private fun currencyIdValue(id: String): String = "{\"cryptoCurrencyId\":\"$id\"}"
|
||||
|
||||
private companion object {
|
||||
const val BS = '\u27E8' // ⟨ body start
|
||||
const val BE = '\u27E9' // ⟩ body end
|
||||
const val DP = '\u2192' // → derivation path delimiter
|
||||
const val CA = '\u2693' // ⚓ contract address delimiter
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue