Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ internal class GiveBillTransactor(
val freshExchange = freshState.exchangeDataFor(
amount = sendingAmount,
mint = desiredToken.address,
billExchangeDataTimeout = exchangeDataTimeout
billExchangeDataTimeout = null // relaxed — we just fetched this rate
) ?: return logAndFail(GiveTransactorError.ExchangeRateExpiredException())

freshState to freshExchange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
Expand All @@ -29,6 +30,8 @@ import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@OptIn(ExperimentalCoroutinesApi::class)
class GiveBillTransactorTest {
Expand Down Expand Up @@ -134,6 +137,45 @@ class GiveBillTransactorTest {
unmockkStatic("com.getcode.opencode.internal.extensions.VerifiedStateKt")
}

@Test
fun `retry uses relaxed timeout for freshly resolved exchange data`() = runTest {
val transactor = createTransactor(this)
val staleState = mockk<VerifiedState>(relaxed = true)
// Set a strict 30s timeout — mirrors the server-configured value
setupWith(transactor, verifiedState = staleState, billExchangeDataTimeout = 30.seconds)

mockkStatic("com.getcode.opencode.internal.extensions.VerifiedStateKt")
// Initial: rate expired with strict 30s timeout
every { staleState.exchangeDataFor(any<LocalFiat>(), any<Mint>(), any()) } returns null

// Fresh state: valid exchange data available
val freshState = mockk<VerifiedState>(relaxed = true)
every { freshState.exchangeDataFor(any<LocalFiat>(), any<Mint>(), any()) } returns mockk<ExchangeData.Verified>(relaxed = true)
coEvery {
verifiedFiatCalculator.resolveVerifiedState(any<CurrencyCode>(), any<Mint>())
} returns freshState

coEvery {
messagingController.sendRequestToGiveBill(any(), any(), any())
} returns Result.success(mockk(relaxed = true))

coEvery {
messagingController.awaitRequestToGrabBill(any(), any())
} returns null

val result = transactor.start()

// Should proceed past exchange resolution and fail at grab — NOT ExchangeRateExpiredException
assertTrue(result.isFailure)
assertIs<GiveBillTransactor.GiveTransactorError.NoGrabReceived>(result.exceptionOrNull())

// Verify the retry path called exchangeDataFor with null (relaxed) timeout,
// NOT the strict 30s billExchangeDataTimeout
verify { freshState.exchangeDataFor(any<LocalFiat>(), any<Mint>(), isNull()) }

unmockkStatic("com.getcode.opencode.internal.extensions.VerifiedStateKt")
}

@Test
fun `start fails when send give bill fails`() = runTest {
val transactor = createTransactor(this)
Expand Down Expand Up @@ -268,6 +310,7 @@ class GiveBillTransactorTest {
transactor: GiveBillTransactor,
verifiedState: VerifiedState? = null,
nonce: List<Byte>? = null,
billExchangeDataTimeout: Duration? = null,
) {
val token = mockk<Token>(relaxed = true) {
every { address } returns Mint.usdf
Expand All @@ -288,7 +331,7 @@ class GiveBillTransactorTest {
token = token,
amount = amount,
owner = owner,
billExchangeDataTimeout = null,
billExchangeDataTimeout = billExchangeDataTimeout,
verifiedState = verifiedState,
providedNonce = nonce,
)
Expand Down
Loading