gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] 01/02: Testing the EBICS time-framed history request.


From: gnunet
Subject: [libeufin] 01/02: Testing the EBICS time-framed history request.
Date: Fri, 28 Jul 2023 17:11:55 +0200

This is an automated email from the git hooks/post-receive script.

ms pushed a commit to branch master
in repository libeufin.

commit d72290e523da8aa1dda3f072e3ecf232700f4cfe
Author: MS <ms@taler.net>
AuthorDate: Fri Jul 28 16:57:34 2023 +0200

    Testing the EBICS time-framed history request.
---
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |  4 +-
 .../main/kotlin/tech/libeufin/nexus/Scheduling.kt  |  3 +-
 .../tech/libeufin/nexus/ebics/EbicsClient.kt       |  5 +-
 .../kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt |  5 +-
 .../tech/libeufin/nexus/iso20022/Iso20022.kt       |  1 +
 .../main/kotlin/tech/libeufin/nexus/server/JSON.kt |  2 +-
 .../tech/libeufin/nexus/server/NexusServer.kt      | 27 ++++++---
 nexus/src/test/kotlin/ConversionServiceTest.kt     | 11 ----
 nexus/src/test/kotlin/MakeEnv.kt                   | 17 +++++-
 nexus/src/test/kotlin/NexusApiTest.kt              | 64 +++++++++++++++++++++-
 nexus/src/test/kotlin/SandboxAccessApiTest.kt      |  3 +
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  |  7 ++-
 .../kotlin/tech/libeufin/sandbox/bankAccount.kt    |  4 +-
 util/src/main/kotlin/Ebics.kt                      |  9 ++-
 util/src/main/kotlin/time.kt                       | 10 ++++
 15 files changed, 135 insertions(+), 37 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 0886e177..5b453530 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -30,6 +30,7 @@ import com.github.ajalt.clikt.parameters.types.int
 import execThrowableOrTerminate
 import com.github.ajalt.clikt.core.*
 import com.github.ajalt.clikt.parameters.options.*
+import io.ktor.server.application.*
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -38,7 +39,6 @@ import tech.libeufin.nexus.iso20022.NexusPaymentInitiationData
 import tech.libeufin.nexus.iso20022.createPain001document
 import tech.libeufin.nexus.iso20022.parseCamtMessage
 import tech.libeufin.nexus.server.EbicsDialects
-import tech.libeufin.nexus.server.client
 import tech.libeufin.nexus.server.nexusApp
 import tech.libeufin.util.*
 import java.io.File
@@ -77,7 +77,7 @@ class Serve : CliktCommand("Run nexus HTTP server") {
     override fun run() {
         setLogLevel(logLevel)
         execThrowableOrTerminate { 
dbCreateTables(getDbConnFromEnv(NEXUS_DB_ENV_VAR_NAME)) }
-        CoroutineScope(Dispatchers.IO).launch(fallback) { 
whileTrueOperationScheduler(client) }
+        CoroutineScope(Dispatchers.IO).launch(fallback) { 
whileTrueOperationScheduler() }
         if (withUnixSocket != null) {
             startServer(
                 withUnixSocket!!,
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Scheduling.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Scheduling.kt
index 8b413b44..2e67eb3b 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Scheduling.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Scheduling.kt
@@ -30,6 +30,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
 import tech.libeufin.nexus.bankaccount.fetchBankAccountTransactions
 import tech.libeufin.nexus.bankaccount.submitAllPaymentInitiations
 import tech.libeufin.nexus.server.FetchSpecJson
+import tech.libeufin.nexus.server.client
 import java.lang.IllegalArgumentException
 import java.time.Duration
 import java.time.Instant
@@ -162,7 +163,7 @@ suspend fun javaTimerOperationScheduler(httpClient: 
HttpClient) {
 }
 */
 
-suspend fun whileTrueOperationScheduler(httpClient: HttpClient) {
+suspend fun whileTrueOperationScheduler(httpClient: HttpClient = client) {
     while (true) {
         operationScheduler(httpClient)
         // Wait the shortest period that the cron spec would allow.
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
index e2706d8b..e2fe7264 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
@@ -46,18 +46,17 @@ private suspend inline fun HttpClient.postToBank(url: 
String, body: String): Str
                 setBody(body)
         }
     } catch (e: ClientRequestException) {
-        logger.error(e.message)
+        logger.error("Exception during request to $url: ${e.message}")
         val returnStatus = if (e.response.status.value == 
HttpStatusCode.RequestTimeout.value)
             HttpStatusCode.GatewayTimeout
         else HttpStatusCode.BadGateway
-        
         throw NexusError(
             returnStatus,
             e.message
         )
     }
     catch (e: Exception) {
-        logger.error("Exception during request ${e.message}")
+        logger.error("Exception during request to $url: ${e.message}")
         throw NexusError(
             HttpStatusCode.BadGateway,
             e.message ?: "Could not reach the bank"
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
index fbb128f8..0f007d77 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
@@ -681,8 +681,9 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol {
             is FetchSpecTimeRangeJson -> {
                 // the parse() method defaults to the YYYY-MM-DD format.
                 // If parsing fails, the global catcher intervenes.
-                val start: LocalDate = LocalDate.parse(fetchSpec.start)
-                val end: LocalDate = LocalDate.parse(fetchSpec.end)
+
+                val start: LocalDate = parseDashedDate(fetchSpec.start)
+                val end: LocalDate = parseDashedDate(fetchSpec.end)
                 val p = EbicsStandardOrderParams(
                     EbicsDateRange(
                         start = 
start.atStartOfDay().atZone(ZoneId.systemDefault()),
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
index d5229d94..128b7a1b 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
@@ -59,6 +59,7 @@ import java.time.LocalDateTime
 import java.time.ZoneId
 import java.time.ZonedDateTime
 import java.time.format.DateTimeFormatter
+import tech.libeufin.nexus.logger
 
 
 enum class CashManagementResponseType(@get:JsonValue val jsonName: String) {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt
index a0dc6b0c..39955778 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt
@@ -227,7 +227,7 @@ class FetchSpecTimeRangeJson(
     start: String,
     end: String,
     bankConnection: String?
-) : FetchSpecJson(level, bankConnection)
+) : FetchSpecJson(level, bankConnection, start, end)
 
 @JsonTypeName("previous-days")
 class FetchSpecPreviousDaysJson(level: FetchLevel, bankConnection: String?, 
val number: Int) :
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
index 7f0fa09f..a9d31e63 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
@@ -47,6 +47,7 @@ import tech.libeufin.nexus.ebics.*
 import tech.libeufin.nexus.iso20022.ingestCamtMessageIntoAccount
 import tech.libeufin.util.*
 import java.net.URLEncoder
+import tech.libeufin.nexus.logger
 
 // Return facade state depending on the type.
 fun getFacadeState(type: String, facade: FacadeEntity): JsonNode {
@@ -780,16 +781,26 @@ val nexusApp: Application.() -> Unit = {
              * fetches, it is ALSO possible that although one error is 
reported,
              * SOME transactions made it to the database!
              */
-            if (ingestionResult.errors != null)
-            /**
-             * Nexus could not handle the error (regardless of it being 
generated
-             * here or gotten from the bank).  The response body should inform 
the
-             * client about what failed.
-             */
-            statusCode = HttpStatusCode.InternalServerError
+            if (ingestionResult.errors != null) {
+                /**
+                 * Nexus could not handle the error (regardless of it being 
generated
+                 * here or gotten from the bank).  The response body should 
inform the
+                 * client about what failed.
+                 */
+                statusCode = HttpStatusCode.InternalServerError
+            }
+
             call.respond(
                 status = statusCode,
-                ingestionResult
+                object {
+                    val newTransactions = ingestionResult.newTransactions
+                    val downloadedTransactions = 
ingestionResult.downloadedTransactions
+                    val errors = mutableListOf<String>().apply {
+                        ingestionResult.errors?.forEach {
+                            this.add(it.message ?: "Error message not found.")
+                        }
+                    }
+                }
             )
             return@post
         }
diff --git a/nexus/src/test/kotlin/ConversionServiceTest.kt 
b/nexus/src/test/kotlin/ConversionServiceTest.kt
index bec0d4e9..f38dece0 100644
--- a/nexus/src/test/kotlin/ConversionServiceTest.kt
+++ b/nexus/src/test/kotlin/ConversionServiceTest.kt
@@ -238,17 +238,6 @@ class ConversionServiceTest {
         }
     }
 
-    // Abstracts the mock handler installation.
-    private fun getMockedClient(handler: 
MockRequestHandleScope.(HttpRequestData) -> HttpResponseData): HttpClient {
-        return HttpClient(MockEngine) {
-            followRedirects = false
-            engine {
-                addHandler {
-                        request -> handler(request)
-                }
-            }
-        }
-    }
     /**
      * Checks that the cash-out monitor reacts after
      * a CRDT transaction arrives at the designated account.
diff --git a/nexus/src/test/kotlin/MakeEnv.kt b/nexus/src/test/kotlin/MakeEnv.kt
index eb84e628..32c6e607 100644
--- a/nexus/src/test/kotlin/MakeEnv.kt
+++ b/nexus/src/test/kotlin/MakeEnv.kt
@@ -1,4 +1,7 @@
 import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import io.ktor.client.*
+import io.ktor.client.engine.mock.*
+import io.ktor.client.request.*
 import org.jetbrains.exposed.sql.Database
 import org.jetbrains.exposed.sql.statements.api.ExposedBlob
 import org.jetbrains.exposed.sql.transactions.TransactionManager
@@ -754,4 +757,16 @@ val poFiCamt054_2019_incoming: String = """
                </Ntfctn>
        </BkToCstmrDbtCdtNtfctn>
 </Document>
-""".trimIndent()
\ No newline at end of file
+""".trimIndent()
+
+// Abstracts the mock handler installation.
+fun getMockedClient(handler: MockRequestHandleScope.(HttpRequestData) -> 
HttpResponseData): HttpClient {
+    return HttpClient(MockEngine) {
+        followRedirects = false
+        engine {
+            addHandler {
+                    request -> handler(request)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/NexusApiTest.kt 
b/nexus/src/test/kotlin/NexusApiTest.kt
index ab6dcb34..899f6dad 100644
--- a/nexus/src/test/kotlin/NexusApiTest.kt
+++ b/nexus/src/test/kotlin/NexusApiTest.kt
@@ -1,9 +1,12 @@
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+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.application.*
+import io.ktor.server.config.*
 import io.ktor.server.testing.*
 import io.netty.handler.codec.http.HttpResponseStatus
 import kotlinx.coroutines.async
@@ -13,7 +16,14 @@ import kotlinx.coroutines.runBlocking
 import org.jetbrains.exposed.sql.transactions.transaction
 import org.junit.Test
 import tech.libeufin.nexus.PaymentInitiationEntity
-import tech.libeufin.nexus.server.nexusApp
+import tech.libeufin.nexus.bankaccount.ingestBankMessagesIntoAccount
+import tech.libeufin.nexus.getConnectionPlugin
+import tech.libeufin.nexus.iso20022.ingestCamtMessageIntoAccount
+import tech.libeufin.nexus.server.*
+import tech.libeufin.sandbox.BankAccountTransactionEntity
+import tech.libeufin.sandbox.BankAccountTransactionsTable
+import tech.libeufin.sandbox.sandboxApp
+import tech.libeufin.sandbox.wireTransfer
 
 /**
  * This class tests the API offered by Nexus,
@@ -143,7 +153,6 @@ class NexusApiTest {
                     contentType(ContentType.Application.Json)
                     expectSuccess = true
                     basicAuth("foo", "foo")
-                    // NOTE: current API doesn't allow to omit the 'params' 
field.
                     setBody("""{
                         "name": "send-payments",
                         "cronspec": "* * *",
@@ -205,7 +214,58 @@ class NexusApiTest {
                     expectSuccess = false
                 }
                 assert(maybeConflict.status.value == 
HttpStatusCode.Conflict.value)
+            }
+        }
+    }
+    @Test
+    fun timeRangeFetch() {
+        withTestDatabase {
+            prepSandboxDb()
+            prepNexusDb()
+            val ref = wireTransfer(
+                "admin",
+                "foo",
+                subject = "past payment",
+                amount = "TESTKUDOS:30"
+                )
+            transaction {
+                BankAccountTransactionEntity.find {
+                    BankAccountTransactionsTable.accountServicerReference eq 
ref
+                }.first().date = 1577833200000L // Jan, 1st, 2020
+            }
+            testApplication {
+                application(sandboxApp)
+                val conn = getConnectionPlugin("ebics")
 
+                // Asking a time range where the one payment is expected to 
exist
+                conn.fetchTransactions(
+                    fetchSpec = FetchSpecTimeRangeJson(
+                        FetchLevel.REPORT,
+                        start = "2019-12-31",
+                        end = "2020-01-02",
+                        bankConnection = null
+                    ),
+                    accountId = "foo",
+                    bankConnectionId = "foo",
+                    client = client
+                )
+                val res = ingestBankMessagesIntoAccount("foo", "foo")
+                assert(res.newTransactions == 1)
+                // Asking a time range where the one payment is NOT expected 
to exist
+                conn.fetchTransactions(
+                    fetchSpec = FetchSpecTimeRangeJson(
+                        FetchLevel.REPORT,
+                        start = "2019-10-31",
+                        end = "2020-11-30",
+                        bankConnection = null
+                    ),
+                    accountId = "foo",
+                    bankConnectionId = "foo",
+                    client = client
+                )
+                val resNoData = ingestBankMessagesIntoAccount("foo", "foo")
+                assert(resNoData.downloadedTransactions == 0)
+                assert(resNoData.newTransactions == 0)
             }
         }
     }
diff --git a/nexus/src/test/kotlin/SandboxAccessApiTest.kt 
b/nexus/src/test/kotlin/SandboxAccessApiTest.kt
index b2833890..ac64d327 100644
--- a/nexus/src/test/kotlin/SandboxAccessApiTest.kt
+++ b/nexus/src/test/kotlin/SandboxAccessApiTest.kt
@@ -15,6 +15,7 @@ import org.junit.Ignore
 import org.junit.Test
 import tech.libeufin.nexus.bankaccount.getBankAccount
 import tech.libeufin.sandbox.*
+import tech.libeufin.util.getDatabaseName
 import java.util.*
 import kotlin.concurrent.schedule
 
@@ -250,7 +251,9 @@ class SandboxAccessApiTest {
                 // Check the withdrawal amount in the unique transaction.
                 val t = 
client.get("/demobanks/default/access-api/accounts/foo/transactions") {
                     basicAuth("foo", "foo")
+                    expectSuccess = true
                 }
+                println(t.bodyAsText())
                 val amount = 
mapper.readTree(t.readBytes()).get("transactions").get(0).get("amount").asText()
                 assert(amount == "500000000")
             }
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index bfd521cc..0dc7b742 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -518,6 +518,7 @@ private suspend fun getWithdrawal(call: ApplicationCall) {
 
 private suspend fun confirmWithdrawal(call: ApplicationCall) {
     val withdrawalId = call.expectUriComponent("withdrawal_id")
+    logger.debug("Maybe confirming withdrawal: $withdrawalId")
     transaction {
         val wo = getWithdrawalOperation(withdrawalId)
         if (wo.aborted) throw SandboxError(
@@ -541,6 +542,7 @@ private suspend fun confirmWithdrawal(call: 
ApplicationCall) {
                 "Cannot withdraw without an exchange."
             )
         )
+        logger.debug("Withdrawal ${wo.wopid} confirmed? 
${wo.confirmationDone}")
         if (!wo.confirmationDone) {
             wireTransfer(
                 debitAccount = wo.walletBankAccount,
@@ -1557,15 +1559,18 @@ val sandboxApp: Application.() -> Unit = {
                     var ret: List<XLibeufinBankTransaction> = transaction {
                         extractTxHistory(historyParams)
                     }
+                    logger.debug("Is payment data empty? ${ret.isEmpty()}")
                     // Data was found already, UNLISTEN and respond.
                     if (listenHandle != null && ret.isNotEmpty()) {
+                        logger.debug("No need to wait DB events, payment data 
found.")
                         listenHandle.postgresUnlisten()
                         call.respond(object {val transactions = ret})
                         return@get
                     }
                     // No data was found, sleep until the timeout or getting 
woken up.
                     // Third condition only silences the compiler.
-                    if (listenHandle != null && ret.isEmpty() && longPollMs != 
null) {
+                    if (listenHandle != null && longPollMs != null) {
+                        logger.debug("Waiting DB event for new payment data.")
                         val notificationArrived = 
listenHandle.waitOnIODispatchers(longPollMs)
                         // Only if the awaited event fired, query again the DB.
                         if (notificationArrived)
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
index dc361a52..9a449f84 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
@@ -228,11 +228,9 @@ fun wireTransfer(
         }
 
         // Adjusting the balances (acceptable debit conditions checked before).
-        debitAccount.refresh()
-        creditAccount.refresh()
         // Debit:
         val newDebitBalance = (BigDecimal(debitAccount.balance) - 
amountAsNumber).roundToTwoDigits()
-        debitAccount.balance = newDebitBalance.toPlainString() // FIXME: 
that's ignored!
+        debitAccount.balance = newDebitBalance.toPlainString()
         // Credit:
         val newCreditBalance = (BigDecimal(creditAccount.balance) + 
amountAsNumber).roundToTwoDigits()
         creditAccount.balance = newCreditBalance.toPlainString()
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
index e951016a..036e1c54 100644
--- a/util/src/main/kotlin/Ebics.kt
+++ b/util/src/main/kotlin/Ebics.kt
@@ -37,6 +37,7 @@ import java.math.BigInteger
 import java.security.SecureRandom
 import java.security.interfaces.RSAPrivateCrtKey
 import java.security.interfaces.RSAPublicKey
+import java.time.ZoneId
 import java.time.ZonedDateTime
 import java.util.*
 import java.util.zip.DeflaterInputStream
@@ -382,7 +383,9 @@ fun createEbicsRequestForDownloadInitialization(
         subscriberDetails.partnerId,
         subscriberDetails.hostId,
         nonce,
-        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar(
+            TimeZone.getTimeZone(ZoneId.systemDefault())
+        )),
         subscriberDetails.bankEncPub ?: throw EbicsProtocolError(
             HttpStatusCode.BadRequest,
             "Invalid subscriber state 'bankEncPub' missing, please send HPB 
first"
@@ -415,7 +418,9 @@ fun createEbicsRequestForDownloadInitialization(
         subscriberDetails.partnerId,
         subscriberDetails.hostId,
         nonce,
-        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar(
+            TimeZone.getTimeZone(ZoneId.systemDefault())
+        )),
         subscriberDetails.bankEncPub ?: throw EbicsProtocolError(
             HttpStatusCode.BadRequest,
             "Invalid subscriber state 'bankEncPub' missing, please send HPB 
first"
diff --git a/util/src/main/kotlin/time.kt b/util/src/main/kotlin/time.kt
index bbdb3143..867d1950 100644
--- a/util/src/main/kotlin/time.kt
+++ b/util/src/main/kotlin/time.kt
@@ -52,4 +52,14 @@ fun importDateFromMillis(millis: Long): ZonedDateTime {
 fun LocalDateTime.millis(): Long {
     val instant = Instant.from(this.atZone(ZoneOffset.UTC))
     return instant.toEpochMilli()
+}
+
+fun parseDashedDate(maybeDashedDate: String?): LocalDate {
+    if (maybeDashedDate == null)
+        throw badRequest("dashed date found as null")
+    return try {
+        LocalDate.parse(maybeDashedDate)
+    } catch (e: Exception) {
+        throw badRequest("bad dashed date: $maybeDashedDate.  ${e.message}")
+    }
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]