Updated on 2026-08-14
This commit is contained in:
parent
4dec0b44cd
commit
f19c2a9fd5
14 changed files with 488 additions and 9 deletions
|
|
@ -10,9 +10,9 @@ import com.tangem.tap.data.converter.PendingOfframpEntryConverter
|
|||
import com.tangem.tap.data.model.PendingOfframpEntry
|
||||
import com.tangem.tap.network.exchangeServices.SellService
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Default implementation of [OfframpRepository].
|
||||
|
|
@ -27,7 +27,7 @@ internal class DefaultOfframpRepository(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : OfframpRepository {
|
||||
|
||||
private val pendingOfframpConverter = PendingOfframpEntryConverter()
|
||||
private val converter = PendingOfframpEntryConverter()
|
||||
|
||||
override fun getOfframpUrl(
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
|
|
@ -71,19 +71,21 @@ internal class DefaultOfframpRepository(
|
|||
entry.requestId == requestId &&
|
||||
entry.userWalletId == userWalletId.stringValue &&
|
||||
entry.currencyId == currencyId &&
|
||||
now - entry.createdAt < EXPIRY_MS
|
||||
!entry.isExpired(now)
|
||||
}
|
||||
// Remove only the fully-matched record (single-use); always prune expired ones. A request_id that
|
||||
// matches but with a mismatched wallet/currency is left intact so a tampered redirect cannot burn it.
|
||||
stored.filter { it != matched }.filterNotExpired(now)
|
||||
}
|
||||
matched?.let(pendingOfframpConverter::convert)
|
||||
matched?.let(converter::convert)
|
||||
}
|
||||
|
||||
override suspend fun getAllStoredOfframps(): List<PendingOfframp> = withContext(dispatchers.io) {
|
||||
pendingOfframpStore.data.first().map(converter::convert)
|
||||
}
|
||||
|
||||
private fun List<PendingOfframpEntry>.filterNotExpired(now: Long): List<PendingOfframpEntry> =
|
||||
filter { now - it.createdAt < EXPIRY_MS }
|
||||
filterNot { it.isExpired(now) }
|
||||
|
||||
private companion object {
|
||||
val EXPIRY_MS: Long = TimeUnit.HOURS.toMillis(1)
|
||||
}
|
||||
private fun PendingOfframpEntry.isExpired(now: Long): Boolean = converter.convert(this).isExpired(now)
|
||||
}
|
||||
|
|
@ -236,4 +236,44 @@ internal class DefaultOfframpRepositoryTest {
|
|||
// Assert
|
||||
assertThat(pending).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN registered pending offramps WHEN getAllStoredOfframps THEN returns them without consuming`() = runTest {
|
||||
// Arrange
|
||||
val storedRequestId = repository.registerPendingOfframp(userWalletId, currencyId)
|
||||
|
||||
// Act
|
||||
val stored = repository.getAllStoredOfframps()
|
||||
// ...the record must survive the read so it can still be consumed afterwards
|
||||
val consumed = repository.consumePendingOfframp(storedRequestId, userWalletId, currencyId)
|
||||
|
||||
// Assert
|
||||
assertThat(stored).hasSize(1)
|
||||
assertThat(stored.single().requestId).isEqualTo(storedRequestId)
|
||||
assertThat(stored.single().userWalletId).isEqualTo(userWalletId)
|
||||
assertThat(stored.single().currencyId).isEqualTo(currencyId)
|
||||
assertThat(consumed).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN expired pending offramp WHEN getAllStoredOfframps THEN it is still returned and flagged expired`() =
|
||||
runTest {
|
||||
// Arrange — seed a record created 2 hours ago (past the 1h expiry)
|
||||
val now = System.currentTimeMillis()
|
||||
pendingStoreState.value = listOf(
|
||||
PendingOfframpEntry(
|
||||
requestId = "expired-id",
|
||||
userWalletId = userWalletId.stringValue,
|
||||
currencyId = currencyId,
|
||||
createdAt = now - TimeUnit.HOURS.toMillis(2),
|
||||
),
|
||||
)
|
||||
|
||||
// Act
|
||||
val stored = repository.getAllStoredOfframps()
|
||||
|
||||
// Assert
|
||||
assertThat(stored).hasSize(1)
|
||||
assertThat(stored.single().isExpired(now)).isTrue()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue