libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 0c4ea7b8afc1e3ff4d2f2e3348049a98b80f71cc
parent 3eaa7331d444122787ae54ec382b5bf01e3705a1
Author: MS <ms@taler.net>
Date:   Wed, 15 Nov 2023 13:45:40 +0100

nexus fetch

- accepting camt.054 from STDIN for debugging
- enforcing that amounts coming from / going to
  the bank have at most 2 fractional digits.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt | 30+++++++++++++++++++++++-------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt | 2++
Mnexus/src/test/kotlin/Parsing.kt | 26++++++++++++++++++++++++++
Mutil/src/test/kotlin/CryptoUtilTest.kt | 8+++++---
4 files changed, 56 insertions(+), 10 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -159,16 +159,14 @@ fun maybeLogFile( } /** - * Converts the given fractional value to the sub-cent 8 digits - * fraction used in Taler. Note: this value has very likely a <2 - * length, but the function is general, for each fraction with at - * most 8 digits. + * Converts the 2-digits fraction value as given by the bank + * (postfinance dialect), to the Taler 8-digit value (db representation). * * @param bankFrac fractional value * @return the Taler fractional value with at most 8 digits. */ fun makeTalerFrac(bankFrac: String): Int { - if (bankFrac.length > 8) throw Exception("Fractional value has more than 8 digits") + if (bankFrac.length > 2) throw Exception("Fractional value has more than 2 digits") var buf = bankFrac.toIntOrNull() ?: throw Exception("Fractional value not an Int: $bankFrac") repeat(8 - bankFrac.length) { buf *= 10 @@ -355,10 +353,10 @@ fun ingestNotification( content: ByteArray ): Boolean { val incomingPayments = mutableListOf<IncomingPayment>() + val filenamePrefix = "camt.054_P_" // Only these files have all the details. try { content.unzipForEach { fileName, xmlContent -> - // discarding plain "avisierung", since they don't bring any payment subject. - if (!fileName.startsWith("camt.054_P_")) return@unzipForEach + if (!fileName.startsWith(filenamePrefix)) return@unzipForEach val found = findIncomingTxInNotification(xmlContent, ctx.cfg.currency) incomingPayments += found } @@ -510,6 +508,24 @@ class EbicsFetch: CliktCommand("Fetches bank records. Defaults to camt.054 noti if (onlyStatements) whichDoc = SupportedDocument.CAMT_053 if (onlyLogs) whichDoc = SupportedDocument.PAIN_002_LOGS + // If STDIN has data, we run in debug mode: parse, print, and return. + val maybeStdin = generateSequence(::readLine).joinToString("\n") + if (maybeStdin.isNotEmpty()) { + logger.debug("Reading from STDIN, running in debug mode. Not involving the database.") + when(whichDoc) { + SupportedDocument.CAMT_054 -> { + val incoming = findIncomingTxInNotification(maybeStdin, cfg.currency) + incoming.forEach { + println(it) + } + } + else -> { + logger.error("Parsing $whichDoc not supported") + exitProcess(1) + } + } + return + } val ctx = FetchContext( cfg, HttpClient(), diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt @@ -22,6 +22,8 @@ data class Pain001Namespaces( * @return [String] of the amount number without the currency. */ fun getAmountNoCurrency(amount: TalerAmount): String { + if (amount.fraction.toString().length > 8) + throw Exception("Taler amount must have at most 8 fractional digits") if (amount.fraction == 0) { return amount.value.toString() } else { diff --git a/nexus/src/test/kotlin/Parsing.kt b/nexus/src/test/kotlin/Parsing.kt @@ -1,5 +1,8 @@ +import net.taler.wallet.crypto.Base32Crockford import org.junit.Test import org.junit.jupiter.api.assertThrows +import tech.libeufin.nexus.TalerAmount +import tech.libeufin.nexus.getAmountNoCurrency import tech.libeufin.nexus.getTalerAmount import tech.libeufin.nexus.isReservePub import java.lang.StringBuilder @@ -9,6 +12,29 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class Parsing { + @Test // Could be moved in a dedicated Amounts.kt test module. + fun generateCurrencyAgnosticAmount() { + assertThrows<Exception> { + // Too many fractional digits. + getAmountNoCurrency(TalerAmount(1, 123456789, "KUDOS")) + } + assertThrows<Exception> { + // Nexus doesn't support sub-cents. + getAmountNoCurrency(TalerAmount(1, 12345678, "KUDOS")) + } + assertThrows<Exception> { + // Nexus doesn't support sub-cents. + getAmountNoCurrency(TalerAmount(0, 1, "KUDOS")) + } + assertEquals( + "0.01", + getAmountNoCurrency(TalerAmount(0, 1000000, "KUDOS")) + ) + assertEquals( + "0.1", + getAmountNoCurrency(TalerAmount(0, 10000000, "KUDOS")) + ) + } @Test // parses amounts as found in the camt.05x documents. fun parseCurrencyAgnosticAmount() { assertTrue { diff --git a/util/src/test/kotlin/CryptoUtilTest.kt b/util/src/test/kotlin/CryptoUtilTest.kt @@ -156,11 +156,13 @@ class CryptoUtilTest { } @Test(expected = EncodingException::class) - // from Crockford32 encoding to binary. fun base32ToBytesTest() { - val expectedEncoding = "C9P6YRG" + val expectedEncoding = "C9P6YRG" // decodes to 'blob' assert(Base32Crockford.decode(expectedEncoding).toString(Charsets.UTF_8) == "blob") - Base32Crockford.decode("-".repeat(52)) // fulfills the "expected = .." above. + val validKey = "4MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0" + val obj = Base32Crockford.decode(validKey) + val roundTrip = Base32Crockford.encode(obj) + assertEquals(validKey, roundTrip) } @Test