Updated on 2026-08-14
This commit is contained in:
commit
3aaf9c5b0a
2074 changed files with 88068 additions and 12659 deletions
|
|
@ -88,6 +88,7 @@ class ApiConfigTest {
|
|||
appInfoProvider = mockk(),
|
||||
)
|
||||
ApiConfig.ID.SurveySparrow -> SurveySparrow(environmentConfig = environmentConfig)
|
||||
ApiConfig.ID.Auth -> Auth()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue