diff options
author | MS <ms@taler.net> | 2023-05-16 11:17:57 +0200 |
---|---|---|
committer | MS <ms@taler.net> | 2023-05-16 11:17:57 +0200 |
commit | 75777424f450a640998bcb7c8a7823afa5aa49f1 (patch) | |
tree | 0a1163902dbb9660efe638fc10af744a8b3e36b2 | |
parent | 550c2b39faaa8e4384e879b02e6ceabddf24bb36 (diff) | |
download | libeufin-75777424f450a640998bcb7c8a7823afa5aa49f1.tar.gz libeufin-75777424f450a640998bcb7c8a7823afa5aa49f1.tar.bz2 libeufin-75777424f450a640998bcb7c8a7823afa5aa49f1.zip |
fix cash-out currency
-rw-r--r-- | nexus/src/test/kotlin/ConversionServiceTest.kt | 75 | ||||
-rw-r--r-- | sandbox/src/main/kotlin/tech/libeufin/sandbox/ConversionService.kt | 40 |
2 files changed, 79 insertions, 36 deletions
diff --git a/nexus/src/test/kotlin/ConversionServiceTest.kt b/nexus/src/test/kotlin/ConversionServiceTest.kt index aedf7f85..39222d7a 100644 --- a/nexus/src/test/kotlin/ConversionServiceTest.kt +++ b/nexus/src/test/kotlin/ConversionServiceTest.kt @@ -1,5 +1,11 @@ +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.ktor.client.* import io.ktor.client.engine.mock.* +import io.ktor.client.plugins.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* import io.ktor.server.testing.* import kotlinx.coroutines.* import org.jetbrains.exposed.sql.and @@ -8,6 +14,7 @@ import org.junit.Ignore import org.junit.Test import tech.libeufin.nexus.server.nexusApp import tech.libeufin.sandbox.* +import tech.libeufin.util.parseAmount class ConversionServiceTest { /** @@ -70,16 +77,19 @@ class ConversionServiceTest { * and one CRDT related to the "exchange-0" account. Thus filtering * the direction is also required. */ - assert(BankAccountTransactionEntity.find { - BankAccountTransactionsTable.creditorIban eq "AT561936082973364859" and ( + assert( + BankAccountTransactionEntity.find { + BankAccountTransactionsTable.creditorIban eq "AT561936082973364859" and ( BankAccountTransactionsTable.direction eq "CRDT" - ) - }.count() == 1L) - - // Asserting that the one incoming transactino has the wired reserve public key. - assert(BankAccountTransactionEntity.find { + ) + }.count() == 1L + ) + val boughtIn = BankAccountTransactionEntity.find { BankAccountTransactionsTable.creditorIban eq "AT561936082973364859" - }.first().subject == reservePub) + }.first() + // Asserting that the one incoming transaction has the wired reserve public key + // and the regional currency. + assert(boughtIn.subject == reservePub && boughtIn.currency == "REGIO") } } } @@ -91,16 +101,42 @@ class ConversionServiceTest { @Test fun cashoutTest() { withTestDatabase { - prepSandboxDb() + prepSandboxDb( + currency = "REGIO", + cashoutCurrency = "FIAT" + ) prepNexusDb() wireTransfer( debitAccount = "foo", creditAccount = "admin", subject = "fiat #0", - amount = "TESTKUDOS:3" + amount = "REGIO:3" ) testApplication { application(nexusApp) + /** + * This construct allows to capture the HTTP request that the cash-out + * monitor (that runs in Sandbox) does to Nexus letting check that the + * currency mentioned in the fiat payment initiations is indeed the fiat + * currency. + */ + val checkCurrencyClient = HttpClient(MockEngine) { + engine { + addHandler { + request -> + if (request.url.encodedPath == "/bank-accounts/foo/payment-initiations" && request.method == HttpMethod.Post) { + val body = jacksonObjectMapper().readTree(request.body.toByteArray()) + val postedAmount = body.get("amount").asText() + assert(parseAmount(postedAmount).currency == "FIAT") + respondOk("cash-out-nonce") + } else { + println("Cash-out monitor wrongly requested to: ${request.url}") + // This is a minimal Web server that support only the above endpoint. + respondError(status = HttpStatusCode.NotImplemented) + } + } + } + } runBlocking { val job = launch { /** @@ -118,17 +154,24 @@ class ConversionServiceTest { * jobs that cashoutMonitor internally launches and would escape * the interruptible policy. */ - runBlocking { cashoutMonitor(client) } + runBlocking { cashoutMonitor(checkCurrencyClient) } } } delay(1000L) // Lets DB persist the information. job.cancelAndJoin() + // Checking now the Sandbox side, and namely that one + // cash-out operation got carried out. + transaction { + assert(CashoutSubmissionEntity.all().count() == 1L) + val op = CashoutSubmissionEntity.all().first() + /** + * The next assert witnesses that the mock client's + * currency assert succeeded. + */ + assert(op.maybeNexusResposnse == "cash-out-nonce") + } } } - transaction { - assert(CashoutSubmissionEntity.all().count() == 1L) - assert(CashoutSubmissionEntity.all().first().isSubmitted) - } } } @@ -171,4 +214,4 @@ class ConversionServiceTest { } } } -}
\ No newline at end of file +} diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/ConversionService.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/ConversionService.kt index a6572b57..bbbae1df 100644 --- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/ConversionService.kt +++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/ConversionService.kt @@ -249,6 +249,19 @@ suspend fun cashoutMonitor( val config = demobank?.config ?: throw internalServerError( "Demobank '$demobankName' has no configuration." ) + /** + * The monitor needs the cash-out currency to correctly POST + * payment initiations at Nexus. Recall: Nexus bank accounts + * do not mandate any particular currency, as they serve as mere + * bridges to the backing bank. And: a backing bank may have + * multiple currencies, or the backing bank may not explicitly + * specify any currencies to be _the_ currency of the backed + * bank account. + */ + if (config.cashoutCurrency == null) { + logger.error("Config lacks cash-out currency.") + exitProcess(1) + } val nexusBaseUrl = getConfigValueOrThrow(config::nexusBaseUrl) val usernameAtNexus = getConfigValueOrThrow(config::usernameAtNexus) val passwordAtNexus = getConfigValueOrThrow(config::passwordAtNexus) @@ -292,8 +305,8 @@ suspend fun cashoutMonitor( */ val uid = it.accountServicerReference val iban = it.creditorIban - val bic = it.debtorBic - val amount = "${it.currency}:${it.amount}" + val bic = it.creditorBic + val amount = "${config.cashoutCurrency}:${it.amount}" // FIXME: need fiat currency here. val subject = it.subject val name = it.creditorName } @@ -328,20 +341,6 @@ suspend fun cashoutMonitor( if (resp.status.value != HttpStatusCode.OK.value) { logger.error("Cash-out monitor, unhandled response status: ${resp.status.value}. Fail Sandbox") exitProcess(1) - - // Previous versions use to store the faulty transaction - // and continue the execution. The block below shows how - // to do that. - - /*transaction { - CashoutSubmissionEntity.new { - localTransaction = it.id - this.hasErrors = true - if (maybeResponseBody.isNotEmpty()) - this.maybeNexusResposnse = maybeResponseBody - } - bankAccount.lastFiatSubmission = it - }*/ } // Successful case, mark the wire transfer as submitted, // and advance the pointer to the last submitted payment. @@ -352,10 +351,11 @@ suspend fun cashoutMonitor( hasErrors = false submissionTime = resp.responseTime.timestamp isSubmitted = true - // Expectedly is > 0 and contains the submission - // unique identifier _as assigned by Nexus_. Not - // currently used by Sandbox, but may help to resolve - // disputes. + /** + * The following block associates the submitted payment + * to the UID that Nexus assigned to it. It is currently not + * used in Sandbox, but might help for reconciliation. + */ if (responseBody.isNotEmpty()) maybeNexusResposnse = responseBody } |