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 @@ -135,14 +135,19 @@ internal class ReceiveGiftCardTransactor(
Result.success(Unit)
}

return@timedTraceSuspend userAccountResult.map {
transactionController.receiveRemotely(
giftCard = giftCard,
amount = amount,
owner = requestingTokenOwner,
mint = token.address
)
}.fold(
// fold rather than map: receiveRemotely returns its own Result, and
// mapping would nest it where its failure is never read.
return@timedTraceSuspend userAccountResult.fold(
onSuccess = {
transactionController.receiveRemotely(
giftCard = giftCard,
amount = amount,
owner = requestingTokenOwner,
mint = token.address
)
},
onFailure = { Result.failure(it) },
).fold(
onSuccess = {
onStep("intent")
Result.success(token to amount)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@ import com.getcode.opencode.controllers.TransactionController
import com.getcode.opencode.managers.GiftCardManager
import com.getcode.opencode.managers.MnemonicManager
import com.getcode.opencode.model.accounts.AccountCluster
import com.getcode.opencode.model.accounts.AccountInfo
import com.getcode.opencode.model.accounts.AccountResponse
import com.getcode.opencode.model.core.errors.SubmitIntentError
import com.getcode.opencode.model.financial.DataSource
import com.getcode.opencode.model.financial.Token
import com.getcode.opencode.model.financial.TokenResult
import com.getcode.opencode.model.transactions.ExchangeData
import com.getcode.opencode.providers.TokenMetadataProvider
import com.getcode.solana.keys.Key32
import com.getcode.solana.keys.Mint
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.test.assertSame
import kotlin.test.assertTrue

@OptIn(ExperimentalCoroutinesApi::class)
Expand Down Expand Up @@ -56,11 +65,41 @@ class ReceiveGiftCardTransactorTest {
assertTrue(result.isFailure || result.getOrNull()?.isFailure == true)
}

// Note: Tests that require `accountController.getAccounts()` to return specific
// Result.success(...) values are blocked by a MockK bug with Result<T> inline class
// returns on suspend functions (https://github.com/mockk/mockk/issues/937).
// Those tests should use fake repositories + real controllers when the project
// establishes that pattern for transactor-level tests.
// endregion

// region intent submission

@Test
fun `start fails with the stale state when the gift card was already claimed`() = runTest {
setupWithOwner()
setupClaimableGiftCard()
val alreadyClaimed = SubmitIntentError.StaleState(
listOf("gift card balance has already been claimed")
)
coEvery {
transactionController.receiveRemotely(any(), any(), any(), any())
} returns Result.failure(alreadyClaimed)

val result = transactor.start(claimIfOwned = false)

assertTrue(result.isFailure)
assertSame(alreadyClaimed, result.exceptionOrNull())
}

@Test
fun `start fails when the receive intent is rejected`() = runTest {
setupWithOwner()
setupClaimableGiftCard()
val denied = SubmitIntentError.Denied(listOf("denied"))
coEvery {
transactionController.receiveRemotely(any(), any(), any(), any())
} returns Result.failure(denied)

val result = transactor.start(claimIfOwned = false)

assertTrue(result.isFailure)
assertSame(denied, result.exceptionOrNull())
}

// endregion

Expand Down Expand Up @@ -93,5 +132,30 @@ class ReceiveGiftCardTransactorTest {
transactor.with(owner, "test-entropy")
}

private fun setupClaimableGiftCard() {
val mint = Mint("giftMint11111111111111111111111111111111111")
val info = mockk<AccountInfo>(relaxed = true) {
every { claimState } returns AccountInfo.ClaimState.NotClaimed
every { isGiftCardIssuer } returns false
every { this@mockk.mint } returns mint
every { originalExchangeData } returns ExchangeData.WithRate(
currencyCode = "usd",
exchangeRate = 1.0,
nativeAmount = 5.0,
quarks = 5_000_000,
mint = mint,
)
}
val token = mockk<Token>(relaxed = true) {
every { address } returns mint
}
coEvery {
accountController.getAccounts(any(), any(), any())
} returns Result.success(AccountResponse(mapOf(Key32.mock to info)))
coEvery { tokenProvider.getTokenMetadata(mint) } returns
Result.success(TokenResult(token, DataSource.Network))
every { accountController.hasAccountFor(mint) } returns true
}

// endregion
}
Loading