Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-01 21:31:52 +04:00
commit 3aaf9c5b0a
2074 changed files with 88068 additions and 12659 deletions

View file

@ -88,6 +88,7 @@ class ApiConfigTest {
appInfoProvider = mockk(),
)
ApiConfig.ID.SurveySparrow -> SurveySparrow(environmentConfig = environmentConfig)
ApiConfig.ID.Auth -> Auth()
}
}
}

View file

@ -130,6 +130,7 @@ internal class ProdApiConfigsManagerTest {
appInfoProvider = appInfoProvider,
)
ApiConfig.ID.SurveySparrow -> SurveySparrow(environmentConfig = environmentConfig)
ApiConfig.ID.Auth -> Auth()
}
}
}
@ -148,9 +149,35 @@ internal class ProdApiConfigsManagerTest {
ApiConfig.ID.News -> createNewsModel()
ApiConfig.ID.GaslessTxService -> createGaslessTxServiceModel()
ApiConfig.ID.SurveySparrow -> createSurveySparrowModel()
ApiConfig.ID.Auth -> createAuthModel()
}
}
private fun createAuthModel(): TestModel {
val environment = when (BuildConfig.BUILD_TYPE) {
MOCKED_BUILD_TYPE,
DEBUG_BUILD_TYPE,
INTERNAL_BUILD_TYPE,
-> ApiEnvironment.DEV
EXTERNAL_BUILD_TYPE,
RELEASE_BUILD_TYPE,
-> ApiEnvironment.PROD
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
}
return TestModel(
id = ApiConfig.ID.Auth,
expected = ApiEnvironmentConfig(
environment = environment,
baseUrl = when (environment) {
ApiEnvironment.PROD -> "https://authentication.tangem.org/"
else -> "[REDACTED_ENV_URL]"
},
headers = emptyMap(),
),
)
}
private fun createExpressModel(): TestModel {
val environment = when (BuildConfig.BUILD_TYPE) {
DEBUG_BUILD_TYPE,
@ -294,6 +321,7 @@ internal class ProdApiConfigsManagerTest {
private fun createGaslessTxServiceModel(): TestModel {
val (environment, baseUrl) = when (BuildConfig.BUILD_TYPE) {
MOCKED_BUILD_TYPE,
-> ApiEnvironment.MOCK to "[REDACTED_ENV_URL]"
DEBUG_BUILD_TYPE,
-> ApiEnvironment.DEV to "[REDACTED_ENV_URL]"
INTERNAL_BUILD_TYPE,

View file

@ -14,7 +14,7 @@ import io.mockk.coVerifyOrder
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.jupiter.api.Test
/**
[REDACTED_AUTHOR]

View file

@ -5,7 +5,7 @@ import com.google.common.truth.Truth
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.jupiter.api.Test
import java.io.IOException
/**

View file

@ -0,0 +1,107 @@
package com.tangem.datasource.local.card
import com.google.common.truth.Truth.assertThat
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import com.tangem.test.core.ProvideTestModels
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
/**
* Tests Moshi serialization/deserialization of [UsedCardInfo].
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class UsedCardInfoSerializationTest {
private val adapter = Moshi.Builder().build().adapter(UsedCardInfo::class.java)
@Test
fun `GIVEN full model WHEN toJson THEN all fields serialized in declaration order`() {
// Arrange
val model = UsedCardInfo(
cardId = "card-1",
isScanned = true,
isActivationStarted = true,
isActivationFinished = false,
hasBackupError = true,
)
// Act
val json = adapter.toJson(model)
// Assert
assertThat(json).isEqualTo(
"""{"cardId":"card-1","isScanned":true,"isActivationStarted":true,""" +
""""isActivationFinished":false,"hasBackupError":true}""",
)
}
@Test
fun `GIVEN full json WHEN fromJson THEN model fully populated`() {
// Arrange
val json = """{"cardId":"card-2","isScanned":false,"isActivationStarted":true,""" +
""""isActivationFinished":true,"hasBackupError":false}"""
// Act
val result = adapter.fromJson(json)
// Assert
assertThat(result).isEqualTo(
UsedCardInfo(
cardId = "card-2",
isScanned = false,
isActivationStarted = true,
isActivationFinished = true,
hasBackupError = false,
),
)
}
@Test
fun `GIVEN json with only cardId WHEN fromJson THEN boolean fields fall back to defaults`() {
// Arrange
val json = """{"cardId":"card-3"}"""
// Act
val result = adapter.fromJson(json)
// Assert
assertThat(result).isEqualTo(UsedCardInfo(cardId = "card-3"))
}
@Test
fun `GIVEN json without cardId WHEN fromJson THEN throws`() {
// Arrange
val json = """{"isScanned":true}"""
// Act
val error = runCatching { adapter.fromJson(json) }.exceptionOrNull()
// Assert
assertThat(error).isInstanceOf(JsonDataException::class.java)
}
@ParameterizedTest
@ProvideTestModels
fun roundTrip(model: UsedCardInfo) {
// Act
val restored = adapter.fromJson(adapter.toJson(model))
// Assert
assertThat(restored).isEqualTo(model)
}
private fun provideTestModels() = listOf(
UsedCardInfo(cardId = "default-only"),
UsedCardInfo(
cardId = "all-true",
isScanned = true,
isActivationStarted = true,
isActivationFinished = true,
hasBackupError = true,
),
UsedCardInfo(cardId = "activation-in-progress", isScanned = true, isActivationStarted = true),
UsedCardInfo(cardId = "backup-error", hasBackupError = true),
)
}

View file

@ -0,0 +1,276 @@
package com.tangem.datasource.local.converter
import com.google.common.truth.Truth
import com.tangem.datasource.api.express.models.response.ExchangeItemResponse
import com.tangem.datasource.api.onramp.models.response.OnrampItemResponse
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class ExpressHistoryConverterTest {
@Test
fun `GIVEN exchange item WHEN toEntity THEN all transaction fields are mapped`() {
// GIVEN
val item = createExchangeItem()
// WHEN
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
// THEN
Truth.assertThat(entity.txId).isEqualTo(item.txId)
Truth.assertThat(entity.ownerAddress).isEqualTo(OWNER_ADDRESS)
Truth.assertThat(entity.providerId).isEqualTo(item.providerId)
Truth.assertThat(entity.fromAddress).isEqualTo(item.fromAddress)
Truth.assertThat(entity.payinAddress).isEqualTo(item.payinAddress)
Truth.assertThat(entity.payinExtraId).isEqualTo(item.payinExtraId)
Truth.assertThat(entity.payoutAddress).isEqualTo(item.payoutAddress)
Truth.assertThat(entity.refundAddress).isEqualTo(item.refundAddress)
Truth.assertThat(entity.refundExtraId).isEqualTo(item.refundExtraId)
Truth.assertThat(entity.rateType).isEqualTo(item.rateType)
Truth.assertThat(entity.externalTxId).isEqualTo(item.externalTxId)
Truth.assertThat(entity.externalTxUrl).isEqualTo(item.externalTxUrl)
Truth.assertThat(entity.payinHash).isEqualTo(item.payinHash)
Truth.assertThat(entity.payoutHash).isEqualTo(item.payoutHash)
Truth.assertThat(entity.refundNetwork).isEqualTo(item.refundNetwork)
Truth.assertThat(entity.refundContractAddress).isEqualTo(item.refundContractAddress)
Truth.assertThat(entity.createdAt).isEqualTo(item.createdAt)
// todo txHistory uncomment
// Truth.assertThat(entity.updatedAt).isEqualTo(item.updatedAt)
Truth.assertThat(entity.payTill).isEqualTo(item.payTill)
Truth.assertThat(entity.averageDuration).isEqualTo(item.averageDuration)
}
@Test
fun `GIVEN exchange item WHEN toEntity THEN status is stored as raw string`() {
// GIVEN
val item = createExchangeItem(status = "finished")
// WHEN
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
// THEN
Truth.assertThat(entity.status).isEqualTo("finished")
}
@Test
fun `GIVEN exchange item WHEN toEntity THEN from and to assets are mapped`() {
// GIVEN
val item = createExchangeItem()
// WHEN
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
// THEN
Truth.assertThat(entity.from.contractAddress).isEqualTo(item.fromContractAddress)
Truth.assertThat(entity.from.network).isEqualTo(item.fromNetwork)
Truth.assertThat(entity.from.decimals).isEqualTo(item.fromDecimals)
Truth.assertThat(entity.from.amount).isEqualTo(item.fromAmount)
// `from` asset never carries an actual amount
Truth.assertThat(entity.from.actualAmount).isNull()
Truth.assertThat(entity.to.contractAddress).isEqualTo(item.toContractAddress)
Truth.assertThat(entity.to.network).isEqualTo(item.toNetwork)
Truth.assertThat(entity.to.decimals).isEqualTo(item.toDecimals)
Truth.assertThat(entity.to.amount).isEqualTo(item.toAmount)
Truth.assertThat(entity.to.actualAmount).isEqualTo(item.toActualAmount)
}
@Test
fun `GIVEN exchange item with null optional fields WHEN toEntity THEN nulls are preserved`() {
// GIVEN
val item = createExchangeItem(
payinExtraId = null,
refundAddress = null,
refundExtraId = null,
externalTxId = null,
externalTxUrl = null,
payinHash = null,
payoutHash = null,
refundNetwork = null,
refundContractAddress = null,
payTill = null,
averageDuration = null,
toActualAmount = null,
)
// WHEN
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
// THEN
Truth.assertThat(entity.payinExtraId).isNull()
Truth.assertThat(entity.refundAddress).isNull()
Truth.assertThat(entity.refundExtraId).isNull()
Truth.assertThat(entity.externalTxId).isNull()
Truth.assertThat(entity.externalTxUrl).isNull()
Truth.assertThat(entity.payinHash).isNull()
Truth.assertThat(entity.payoutHash).isNull()
Truth.assertThat(entity.refundNetwork).isNull()
Truth.assertThat(entity.refundContractAddress).isNull()
Truth.assertThat(entity.payTill).isNull()
Truth.assertThat(entity.averageDuration).isNull()
Truth.assertThat(entity.to.actualAmount).isNull()
}
@Test
fun `GIVEN onramp item WHEN toEntity THEN all transaction fields are mapped`() {
// GIVEN
val item = createOnrampItem()
// WHEN
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
// THEN
Truth.assertThat(entity.txId).isEqualTo(item.txId)
Truth.assertThat(entity.ownerAddress).isEqualTo(OWNER_ADDRESS)
Truth.assertThat(entity.providerId).isEqualTo(item.providerId)
Truth.assertThat(entity.payoutAddress).isEqualTo(item.payoutAddress)
Truth.assertThat(entity.failReason).isEqualTo(item.failReason)
Truth.assertThat(entity.externalTxId).isEqualTo(item.externalTxId)
Truth.assertThat(entity.externalTxUrl).isEqualTo(item.externalTxUrl)
Truth.assertThat(entity.payoutHash).isEqualTo(item.payoutHash)
Truth.assertThat(entity.createdAt).isEqualTo(item.createdAt)
// todo txHistory uncomment
// Truth.assertThat(entity.updatedAt).isEqualTo(item.updatedAt)
Truth.assertThat(entity.fromCurrencyCode).isEqualTo(item.fromCurrencyCode)
Truth.assertThat(entity.fromAmount).isEqualTo(item.fromAmount)
Truth.assertThat(entity.fromPrecision).isEqualTo(item.fromPrecision)
Truth.assertThat(entity.paymentMethod).isEqualTo(item.paymentMethod)
Truth.assertThat(entity.countryCode).isEqualTo(item.countryCode)
}
@Test
fun `GIVEN onramp item WHEN toEntity THEN status is stored as raw string`() {
// GIVEN
val item = createOnrampItem(status = "waiting-for-payment")
// WHEN
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
// THEN
Truth.assertThat(entity.status).isEqualTo("waiting-for-payment")
}
@Test
fun `GIVEN onramp item WHEN toEntity THEN to asset is mapped`() {
// GIVEN
val item = createOnrampItem()
// WHEN
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
// THEN
Truth.assertThat(entity.to.contractAddress).isEqualTo(item.toContractAddress)
Truth.assertThat(entity.to.network).isEqualTo(item.toNetwork)
Truth.assertThat(entity.to.decimals).isEqualTo(item.toDecimals)
Truth.assertThat(entity.to.amount).isEqualTo(item.toAmount)
Truth.assertThat(entity.to.actualAmount).isEqualTo(item.toActualAmount)
}
@Test
fun `GIVEN onramp item with null optional fields WHEN toEntity THEN nulls are preserved`() {
// GIVEN
val item = createOnrampItem(
failReason = null,
externalTxId = null,
externalTxUrl = null,
payoutHash = null,
toAmount = null,
toActualAmount = null,
)
// WHEN
val entity = item.toEntity(ownerAddress = OWNER_ADDRESS)
// THEN
Truth.assertThat(entity.failReason).isNull()
Truth.assertThat(entity.externalTxId).isNull()
Truth.assertThat(entity.externalTxUrl).isNull()
Truth.assertThat(entity.payoutHash).isNull()
Truth.assertThat(entity.to.amount).isNull()
Truth.assertThat(entity.to.actualAmount).isNull()
}
private fun createExchangeItem(
status: String = "waiting",
payinExtraId: String? = "payin-extra",
refundAddress: String? = "refund-address",
refundExtraId: String? = "refund-extra",
externalTxId: String? = "external-tx-id",
externalTxUrl: String? = "https://provider.example/tx",
payinHash: String? = "payin-hash",
payoutHash: String? = "payout-hash",
refundNetwork: String? = "ethereum",
refundContractAddress: String? = "0xrefund",
payTill: String? = "2026-06-01T00:10:00Z",
averageDuration: Long? = 600L,
toActualAmount: String? = "0.99",
) = ExchangeItemResponse(
txId = "exchange-tx-1",
providerId = "changelly",
fromAddress = "0xfrom",
payinAddress = "0xpayin",
payinExtraId = payinExtraId,
payoutAddress = "0xpayout",
refundAddress = refundAddress,
refundExtraId = refundExtraId,
rateType = "float",
status = status,
externalTxId = externalTxId,
externalTxUrl = externalTxUrl,
payinHash = payinHash,
payoutHash = payoutHash,
refundNetwork = refundNetwork,
refundContractAddress = refundContractAddress,
createdAt = "2026-06-01T00:00:00Z",
// todo txHistory uncomment
// updatedAt = "2026-06-01T00:05:00Z",
payTill = payTill,
averageDuration = averageDuration,
fromContractAddress = "0xfromContract",
fromNetwork = "ethereum",
fromDecimals = 18,
fromAmount = "1.0",
toContractAddress = "0xtoContract",
toNetwork = "bitcoin",
toDecimals = 8,
toAmount = "1.0",
toActualAmount = toActualAmount,
)
private fun createOnrampItem(
status: String = "waiting-for-payment",
failReason: String? = "fail-reason",
externalTxId: String? = "external-tx-id",
externalTxUrl: String? = "https://provider.example/tx",
payoutHash: String? = "payout-hash",
toAmount: String? = "0.001",
toActualAmount: String? = "0.00099",
) = OnrampItemResponse(
txId = "onramp-tx-1",
providerId = "mercuryo",
payoutAddress = "0xpayout",
status = status,
failReason = failReason,
externalTxId = externalTxId,
externalTxUrl = externalTxUrl,
payoutHash = payoutHash,
createdAt = "2026-06-01T00:00:00Z",
// todo txHistory uncomment
// updatedAt = "2026-06-01T00:05:00Z",
fromCurrencyCode = "USD",
fromAmount = "100.0",
fromPrecision = 2,
toContractAddress = "0xtoContract",
toNetwork = "bitcoin",
toDecimals = 8,
toAmount = toAmount,
toActualAmount = toActualAmount,
paymentMethod = "card",
countryCode = "US",
)
private companion object {
const val OWNER_ADDRESS = "0xowner"
}
}

View file

@ -0,0 +1,107 @@
package com.tangem.datasource.local.logs
import com.google.common.truth.Truth
import com.tangem.datasource.local.logs.SensitiveUrlMasker.Companion.MASKED_VALUE
import com.tangem.test.core.ProvideTestModels
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class SensitiveUrlMaskerTest {
@ParameterizedTest
@ProvideTestModels
fun mask(model: TestModel) {
// Arrange
val masker = SensitiveUrlMasker(model.sensitiveValues)
// Act
val actual = masker.mask(model.input)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
@Test
fun `mask returns url unchanged when no sensitive values provided`() {
// Arrange
val masker = SensitiveUrlMasker(emptyList())
val url = "https://api.tangem.com/v1/cards/abc123"
// Act
val actual = masker.mask(url)
// Assert
Truth.assertThat(actual).isEqualTo(url)
}
@Test
fun `constructor deduplicates input values`() {
// Arrange — same secret repeated; if no dedup, replace would be invoked twice
// (idempotent on already-masked string, but we assert behavior is identical
// to a single-value masker as a smoke-check)
val withDuplicates = SensitiveUrlMasker(listOf("secret123", "secret123", "secret123"))
val withSingle = SensitiveUrlMasker(listOf("secret123"))
val url = "https://api.tangem.com/?key=secret123"
// Act
val withDup = withDuplicates.mask(url)
val withSingleResult = withSingle.mask(url)
// Assert
Truth.assertThat(withDup).isEqualTo(withSingleResult)
Truth.assertThat(withDup).isEqualTo("https://api.tangem.com/?key=$MASKED_VALUE")
}
private fun provideTestModels() = listOf(
TestModel(
input = "https://api.tangem.com/?key=secret123",
sensitiveValues = listOf("secret123"),
expected = "https://api.tangem.com/?key=$MASKED_VALUE",
),
TestModel(
input = "https://api.tangem.com/?a=alpha&b=beta",
sensitiveValues = listOf("alpha", "beta"),
expected = "https://api.tangem.com/?a=$MASKED_VALUE&b=$MASKED_VALUE",
),
TestModel(
input = "https://api.tangem.com/?key=SECRET123",
sensitiveValues = listOf("secret123"),
expected = "https://api.tangem.com/?key=$MASKED_VALUE",
),
TestModel(
input = "https://api.tangem.com/v1/balance",
sensitiveValues = listOf("notInUrl"),
expected = "https://api.tangem.com/v1/balance",
),
TestModel(
input = "https://api.tangem.com/?key=secret123&other=secret123",
sensitiveValues = listOf("secret123"),
expected = "https://api.tangem.com/?key=$MASKED_VALUE&other=$MASKED_VALUE",
),
TestModel(
input = "https://api.tangem.com/v1/cards",
sensitiveValues = emptyList(),
expected = "https://api.tangem.com/v1/cards",
),
// Regression: when one value is a prefix of another, the longer one must be masked first
// regardless of input order, otherwise the suffix leaks (e.g. "my-node-prod" -> "******-prod").
TestModel(
input = "https://my-node-prod.example.com/v1",
sensitiveValues = listOf("my-node", "my-node-prod"),
expected = "https://$MASKED_VALUE.example.com/v1",
),
TestModel(
input = "https://my-node-prod.example.com/v1",
sensitiveValues = listOf("my-node-prod", "my-node"),
expected = "https://$MASKED_VALUE.example.com/v1",
),
)
data class TestModel(
val input: String,
val sensitiveValues: List<String>,
val expected: String,
)
}

View file

@ -6,7 +6,7 @@ import com.squareup.moshi.adapter
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.datasource.api.common.adapter.BigDecimalAdapter
import dev.onenowy.moshipolymorphicadapter.NamePolymorphicAdapterFactory
import org.junit.Test
import org.junit.jupiter.api.Test
import java.math.BigDecimal
/**
@ -42,10 +42,12 @@ class NetworkStatusDMSerializationTest {
{ "value": "0x123456", "type": "primary" },
{ "value": "0xabcdef", "type": "secondary" }
],
"amounts": { "ETH": "1.2345" },
"yield_supply_statuses": {
"ETH": { "is_active": false, "is_initialized": false, "is_allowed_to_spend": false }
}
"amounts": [
{ "id": { "value": "ethereum" }, "amount": "1.2345" }
],
"yield_supply_statuses": [
{ "id": { "value": "ethereum" }, "is_active": false, "is_initialized": false, "is_allowed_to_spend": false }
]
}
""".trimIndent()
@ -129,10 +131,12 @@ class NetworkStatusDMSerializationTest {
{ "value": "0x123456", "type": "primary" },
{ "value": "0xabcdef", "type": "secondary" }
],
"amounts": { "ETH": "1.2345" },
"yield_supply_statuses": {
"ETH": { "is_active": false, "is_initialized": false, "is_allowed_to_spend": false }
}
"amounts": [
{ "id": { "value": "ethereum" }, "amount": "1.2345" }
],
"yield_supply_statuses": [
{ "id": { "value": "ethereum" }, "is_active": false, "is_initialized": false, "is_allowed_to_spend": false }
]
}
""".stripJsonWhitespace()

View file

@ -0,0 +1,74 @@
package com.tangem.datasource.local.visa.entity
import com.google.common.truth.Truth
import com.squareup.moshi.adapter
import com.tangem.datasource.di.MoshiModule
import org.junit.jupiter.api.Test
import java.math.BigDecimal
/**
* Verifies that the network Moshi (see [MoshiModule.provideNetworkMoshi]) resolves and round-trips the
* polymorphic [VirtualAccountStatusValueDM] adapter.
*
* Guards against the missing `NamePolymorphicAdapterFactory` registration that caused a runtime
* `ClassNotFoundException: ...VirtualAccountStatusValueDMJsonAdapter` (the adapter is registered manually,
* not generated).
*/
class VirtualAccountStatusValueDMSerializationTest {
@OptIn(ExperimentalStdlibApi::class)
private val adapter = MoshiModule().provideNetworkMoshi().adapter<VirtualAccountStatusValueDM>()
@Test
fun `round-trip NotCreated`() {
val model: VirtualAccountStatusValueDM = VirtualAccountStatusValueDM.NotCreated()
val restored = adapter.fromJson(adapter.toJson(model))
Truth.assertThat(restored).isInstanceOf(VirtualAccountStatusValueDM.NotCreated::class.java)
}
@Test
fun `round-trip Provisioning`() {
val model: VirtualAccountStatusValueDM = VirtualAccountStatusValueDM.Provisioning()
val restored = adapter.fromJson(adapter.toJson(model))
Truth.assertThat(restored).isInstanceOf(VirtualAccountStatusValueDM.Provisioning::class.java)
}
@Test
fun `round-trip CountryNotSupported`() {
val model: VirtualAccountStatusValueDM = VirtualAccountStatusValueDM.CountryNotSupported()
val restored = adapter.fromJson(adapter.toJson(model))
Truth.assertThat(restored).isInstanceOf(VirtualAccountStatusValueDM.CountryNotSupported::class.java)
}
@Test
fun `round-trip ActiveAccount`() {
val model: VirtualAccountStatusValueDM = VirtualAccountStatusValueDM.ActiveAccount(
customerId = "cust-1",
currencyCode = "USD",
depositAddress = "0xabc",
fiatBalance = VirtualAccountStatusValueDM.FiatBalanceDM(
availableBalance = BigDecimal("101.56"),
currency = "USD",
),
cryptoBalance = VirtualAccountStatusValueDM.CryptoBalanceDM(
id = "usd-coin",
chainId = 137L,
depositAddress = "0xabc",
tokenContractAddress = "0xdef",
balance = BigDecimal("101.56"),
),
fiatRate = BigDecimal("0.95"),
availableForWithdrawal = BigDecimal("100.00"),
)
val restored = adapter.fromJson(adapter.toJson(model))
Truth.assertThat(restored).isEqualTo(model)
}
}