commit fccc2eff22a0528658d6d1a42ac27e5dd2b4f34c
parent 9d8599f68e063c47eba1a9b1348f79f6357023b2
Author: Antoine A <>
Date: Fri, 19 Apr 2024 07:50:29 +0900
Improve EBICS testbench
Diffstat:
7 files changed, 73 insertions(+), 24 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
@@ -174,7 +174,7 @@ class EbicsSubmit : CliktCommand("Submits any initiated payment found in the dat
try {
submitBatch(ctx, db)
} catch (e: Exception) {
- throw Exception("Failed to submit payments")
+ throw Exception("Failed to submit payments", e)
}
// TODO take submitBatch taken time in the delay
delay(frequency.toKotlinDuration())
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
@@ -277,12 +277,12 @@ sealed interface TxNotification {
/** ISO20022 incoming payment */
data class IncomingPayment(
+ /** ISO20022 AccountServicerReference */
+ val bankId: String,
val amount: TalerAmount,
val wireTransferSubject: String,
- val debitPaytoUri: String,
override val executionTime: Instant,
- /** ISO20022 AccountServicerReference */
- val bankId: String
+ val debitPaytoUri: String
): TxNotification {
override fun toString(): String {
return "IN ${executionTime.fmtDate()} $amount '$bankId' debitor=$debitPaytoUri subject=\"$wireTransferSubject\""
@@ -291,12 +291,12 @@ data class IncomingPayment(
/** ISO20022 outgoing payment */
data class OutgoingPayment(
- val amount: TalerAmount,
- override val executionTime: Instant,
/** ISO20022 MessageIdentification */
val messageId: String,
+ val amount: TalerAmount,
+ val wireTransferSubject: String? = null, // not showing in camt.054
+ override val executionTime: Instant,
val creditPaytoUri: String? = null, // not showing in camt.054
- val wireTransferSubject: String? = null // not showing in camt.054
): TxNotification {
override fun toString(): String {
return "OUT ${executionTime.fmtDate()} $amount '$messageId' creditor=$creditPaytoUri subject=\"$wireTransferSubject\""
@@ -349,6 +349,7 @@ fun parseTx(
var msgId = opt("Refs")?.opt("MsgId")?.text()
val subject = opt("RmtInf")?.map("Ustrd") { text() }?.joinToString("")
var debtorPayto = opt("RltdPties") { payto("Dbtr") }
+ var creditorPayto = opt("RltdPties") { payto("Cdtr") }
RawTx(
kind,
bookDate,
@@ -358,7 +359,8 @@ fun parseTx(
ref,
msgId,
subject,
- debtorPayto
+ debtorPayto,
+ creditorPayto
)
}
}
@@ -388,6 +390,7 @@ fun parseTx(
var msgId = opt("Refs")?.opt("MsgId")?.text()
val subject = opt("RmtInf")?.map("Ustrd") { text() }?.joinToString("")
var debtorPayto = opt("RltdPties") { payto("Dbtr") }
+ var creditorPayto = opt("RltdPties") { payto("Cdtr") }
RawTx(
kind,
bookDate,
@@ -397,7 +400,8 @@ fun parseTx(
ref,
msgId,
subject,
- debtorPayto
+ debtorPayto,
+ creditorPayto
)
}
}
@@ -440,7 +444,8 @@ private data class RawTx(
val ref: String?,
val msgId: String?,
val subject: String?,
- val debtorPayto: String?
+ val debtorPayto: String?,
+ val creditorPayto: String?
)
private class TxErr(val msg: String): Exception(msg)
@@ -478,7 +483,8 @@ private fun parseTxLogic(raw: RawTx): TxNotification {
OutgoingPayment(
amount = raw.amount,
messageId = raw.msgId,
- executionTime = raw.bookDate
+ executionTime = raw.bookDate,
+ creditPaytoUri = raw.creditorPayto
)
}
else -> throw Exception("Unknown transaction notification kind '${raw.kind}'")
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt
@@ -118,7 +118,7 @@ fun generateNewKeys(): ClientPrivateKeysFile =
submitted_ini = false
)
-private inline fun <reified T> persistJsonFile(obj: T, path: Path, name: String) {
+inline fun <reified T> persistJsonFile(obj: T, path: Path, name: String) {
val content = try {
JSON.encodeToString(obj)
} catch (e: Exception) {
@@ -158,7 +158,7 @@ fun persistBankKeys(keys: BankPublicKeysFile, location: Path) = persistJsonFile(
fun persistClientKeys(keys: ClientPrivateKeysFile, location: Path) = persistJsonFile(keys, location, "client private keys")
-private inline fun <reified T> loadJsonFile(path: Path, name: String): T? {
+inline fun <reified T> loadJsonFile(path: Path, name: String): T? {
val content = try {
path.readText()
} catch (e: Exception) {
diff --git a/nexus/src/test/kotlin/helpers.kt b/nexus/src/test/kotlin/helpers.kt
@@ -68,7 +68,7 @@ fun serverSetup(
}
}
-val grothoffPayto = "payto://iban/CH6208704048981247126?receiver-name=Grothoff%20Hans"
+val grothoffPayto = "payto://iban/CH4189144589712575493?receiver-name=Grothoff%20Hans"
val clientKeys = generateNewKeys()
@@ -91,7 +91,7 @@ fun genInitPay(
) = InitiatedPayment(
id = -1,
amount = TalerAmount(44, 0, "KUDOS"),
- creditPaytoUri = "payto://iban/CH9300762011623852957?receiver-name=Test",
+ creditPaytoUri = "payto://iban/CH4189144589712575493?receiver-name=Test",
wireTransferSubject = subject,
initiationTime = Instant.now(),
requestUid = requestUid
@@ -111,7 +111,7 @@ fun genInPay(subject: String) =
fun genOutPay(subject: String, messageId: String) =
OutgoingPayment(
amount = TalerAmount(44, 0, "KUDOS"),
- creditPaytoUri = "payto://iban/CH9300762011623852957?receiver-name=Test",
+ creditPaytoUri = "payto://iban/CH4189144589712575493?receiver-name=Test",
wireTransferSubject = subject,
executionTime = Instant.now(),
messageId = messageId
diff --git a/testbench/README.md b/testbench/README.md
@@ -5,6 +5,7 @@
To add a platform write a minimal configuration file at `testbench/test/PLATFORM/ebics.conf` such as :
``` ini
+# testbench/test/PLATFORM/ebics.conf
[nexus-ebics]
currency = CHF
@@ -30,4 +31,16 @@ make testbench platform=PLATFORM
If HOST_BASE_URL is one a known test platform we will generate and then offer to reset client private keys to test keys registration, otherwise, we will expect existing keys to be found at `testbench/test/PLATFORM/client-ebics-keys.json`.
-This minimal configuration will be augmented on start, you can find the full documentation at `testbench/test/PLATFORM/ebics.edited.conf`.
-\ No newline at end of file
+This minimal configuration will be augmented on start, you can find the full documentation at `testbench/test/PLATFORM/ebics.edited.conf`.
+
+By default, the testbench will use a random dummy IBAN when issuing transactions, but you can specify a real IBAN for real-life testing in the testbench configuration at `testbench/test/config.json` :
+
+``` json
+// testbench/test/PLATFORM/ebics.conf
+{
+ "payto": {
+ "CHF": "payto://iban/CH4189144589712575493?receiver-name=John%20Smith",
+ "EUR": "payto://iban/DE54500105177452372744?receiver-name=John%20Smith"
+ }
+}
+```
diff --git a/testbench/build.gradle b/testbench/build.gradle
@@ -1,6 +1,7 @@
plugins {
id("kotlin")
id("application")
+ id("org.jetbrains.kotlin.plugin.serialization") version "$kotlin_version"
}
java {
diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt
@@ -23,13 +23,17 @@ import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.ProgramResult
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.testing.test
+import io.ktor.http.URLBuilder
+import io.ktor.http.takeFrom
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.Serializable
import tech.libeufin.nexus.LibeufinNexusCommand
import tech.libeufin.nexus.loadBankKeys
import tech.libeufin.nexus.loadClientKeys
import tech.libeufin.nexus.loadConfig
+import tech.libeufin.nexus.loadJsonFile
import kotlin.io.path.*
val nexusCmd = LibeufinNexusCommand()
@@ -64,6 +68,11 @@ data class Kind(val name: String, val settings: String?) {
val test get() = settings != null
}
+@Serializable
+data class Config(
+ val payto: Map<String, String>
+)
+
class Cli : CliktCommand("Run integration tests on banks provider") {
val platform by argument()
@@ -105,6 +114,10 @@ class Cli : CliktCommand("Run integration tests on banks provider") {
else -> Kind("Unknown", null)
}
+ // Read testbench config
+ val benchCfg: Config = loadJsonFile(Path("test/config.json"), "testbench config")
+ ?: Config(emptyMap())
+
// Prepare cmds
val log = "DEBUG"
val flags = " -c $conf -L $log"
@@ -113,11 +126,15 @@ class Cli : CliktCommand("Run integration tests on banks provider") {
val bankKeysPath = cfg.requirePath("nexus-ebics", "bank_public_keys_file")
val currency = cfg.requireString("nexus-ebics", "currency")
- val payto = when (currency) {
- "CHF" -> "payto://iban/CH6208704048981247126?receiver-name=Grothoff%20Hans"
- "EUR" -> "payto://iban/GENODEM1GLS/DE76430609674126675300?receiver-name=Grothoff%20Hans"
- else -> throw Exception("Missing test payto for $currency")
- }
+ val dummyPaytos = mapOf(
+ "CHF" to "payto://iban/CH4189144589712575493?receiver-name=John%20Smith",
+ "EUR" to "payto://iban/DE54500105177452372744?receiver-name=John%20Smith"
+ )
+ val dummyPayto = dummyPaytos[currency]
+ ?: throw Exception("Missing dummy payto for $currency")
+ val payto = benchCfg.payto[currency] ?: dummyPayto
+ ?: throw Exception("Missing test payto for $currency")
+
val recoverDoc = when (cfg.requireString("nexus-ebics", "bank_dialect")) {
"gls" -> "statement"
else -> "notification"
@@ -175,12 +192,25 @@ class Cli : CliktCommand("Run integration tests on banks provider") {
} else {
put("tx", suspend {
step("Submit new transaction")
- // TODO interactive payment editor
nexusCmd.run("initiate-payment $flags \"$payto&amount=$currency:1.1&message=single%20transaction%20test\"")
nexusCmd.run("ebics-submit $ebicsFlags")
Unit
})
}
+ put("tx-bad-name", suspend {
+ val badPayto = URLBuilder().takeFrom(payto)
+ badPayto.parameters.set("receiver-name", "John Smith")
+ step("Submit new transaction with a bad name")
+ nexusCmd.run("initiate-payment $flags \"$badPayto&amount=$currency:1.1&message=This%20should%20fail%20because%20bad%20name\"")
+ nexusCmd.run("ebics-submit $ebicsFlags")
+ Unit
+ })
+ put("tx-dummy", suspend {
+ step("Submit new transaction to a dummy IBAN")
+ nexusCmd.run("initiate-payment $flags \"$dummyPayto&amount=$currency:1.1&message=This%20should%20fail%20because%20dummy\"")
+ nexusCmd.run("ebics-submit $ebicsFlags")
+ Unit
+ })
}
while (true) {
var clientKeys = loadClientKeys(clientKeysPath)