summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMS <ms@taler.net>2023-05-16 11:17:57 +0200
committerMS <ms@taler.net>2023-05-16 11:17:57 +0200
commit75777424f450a640998bcb7c8a7823afa5aa49f1 (patch)
tree0a1163902dbb9660efe638fc10af744a8b3e36b2
parent550c2b39faaa8e4384e879b02e6ceabddf24bb36 (diff)
downloadlibeufin-75777424f450a640998bcb7c8a7823afa5aa49f1.tar.gz
libeufin-75777424f450a640998bcb7c8a7823afa5aa49f1.tar.bz2
libeufin-75777424f450a640998bcb7c8a7823afa5aa49f1.zip
fix cash-out currency
-rw-r--r--nexus/src/test/kotlin/ConversionServiceTest.kt75
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/ConversionService.kt40
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
}