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 @@ -126,15 +126,29 @@ internal class GiveBillTransactor(
val sendingAmount = amount
?: return logAndFail(GiveTransactorError.Other(message = "No amount. Did you call with() first?"))

val verifiedState = providedVerifiedState
val initialState = providedVerifiedState
?: verifiedFiatCalculator.resolveVerifiedState(sendingAmount.rate.currency, desiredToken.address)
?: return logAndFail(GiveTransactorError.Other("Failed to get verified state"))

val exchangeData = verifiedState.exchangeDataFor(
val (verifiedState, exchangeData) = initialState.exchangeDataFor(
amount = sendingAmount,
mint = desiredToken.address,
billExchangeDataTimeout = exchangeDataTimeout
) ?: return logAndFail(GiveTransactorError.ExchangeRateExpiredException())
)?.let { initialState to it }
?: run {
// Rate expired — attempt to resolve a fresh verified state
val freshState = verifiedFiatCalculator.resolveVerifiedState(
sendingAmount.rate.currency, desiredToken.address
) ?: return logAndFail(GiveTransactorError.ExchangeRateExpiredException())

val freshExchange = freshState.exchangeDataFor(
amount = sendingAmount,
mint = desiredToken.address,
billExchangeDataTimeout = exchangeDataTimeout
) ?: return logAndFail(GiveTransactorError.ExchangeRateExpiredException())

freshState to freshExchange
}

// 1. Send request to "give" the bill to the recipient.
// This provides the recipient with the desired token mint of the cash.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,20 @@ class GiveBillTransactorTest {
@Test
fun `start fails when exchange data expired`() = runTest {
val transactor = createTransactor(this)
// Provide verified state directly to skip resolveVerifiedState fallback chain
// Provide verified state directly — its rate is stale
val verifiedState = mockk<VerifiedState>(relaxed = true)
setupWith(transactor, verifiedState = verifiedState)

mockkStatic("com.getcode.opencode.internal.extensions.VerifiedStateKt")
every { verifiedState.exchangeDataFor(any<LocalFiat>(), any<Mint>(), any()) } returns null

// Fresh resolve also returns a stale state so the retry still fails
val freshState = mockk<VerifiedState>(relaxed = true)
every { freshState.exchangeDataFor(any<LocalFiat>(), any<Mint>(), any()) } returns null
coEvery {
verifiedFiatCalculator.resolveVerifiedState(any<CurrencyCode>(), any<Mint>())
} returns freshState

val result = transactor.start()

assertTrue(result.isFailure)
Expand All @@ -91,6 +98,42 @@ class GiveBillTransactorTest {
unmockkStatic("com.getcode.opencode.internal.extensions.VerifiedStateKt")
}

@Test
fun `start refreshes state when exchange data expired`() = runTest {
val transactor = createTransactor(this)
// Provide verified state whose rate is stale
val staleState = mockk<VerifiedState>(relaxed = true)
setupWith(transactor, verifiedState = staleState)

mockkStatic("com.getcode.opencode.internal.extensions.VerifiedStateKt")
every { staleState.exchangeDataFor(any<LocalFiat>(), any<Mint>(), any()) } returns null

// Fresh resolve returns a valid state with fresh exchange data
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

// start() should proceed past exchange data resolution and fail later
// (at awaitRequestToGrabBill) — confirming the fresh resolve succeeded
val result = transactor.start()

assertTrue(result.isFailure)
// Should NOT be ExchangeRateExpiredException — it recovered via fresh state
assertIs<GiveBillTransactor.GiveTransactorError.NoGrabReceived>(result.exceptionOrNull())

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

@Test
fun `start fails when send give bill fails`() = runTest {
val transactor = createTransactor(this)
Expand Down
Loading