commit ae0341f0663684e0828c35a9ce8429f562d6be15 parent 1352e63d09aa2fe2c38e23f015cfadfdd595922e Author: Antoine A <> Date: Tue, 24 Jun 2025 18:30:10 +0200 nexus: more ebics setup test with mocked bank server Diffstat:
15 files changed, 404 insertions(+), 53 deletions(-)
diff --git a/nexus/build.gradle b/nexus/build.gradle @@ -29,6 +29,7 @@ dependencies { // Ktor client library implementation("io.ktor:ktor-server-core:$ktor_version") implementation("io.ktor:ktor-client-cio:$ktor_version") + implementation("io.ktor:ktor-client-mock:$ktor_version") implementation("io.ktor:ktor-client-websockets:$ktor_version") // PDF generation @@ -43,7 +44,7 @@ dependencies { // Unit testing testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version") testImplementation("io.ktor:ktor-server-test-host:$ktor_version") - testImplementation("io.ktor:ktor-client-mock:$ktor_version") + testImplementation("io.ktor:ktor-server-cio:$ktor_version") } application { diff --git a/nexus/conf/fetch.conf b/nexus/conf/fetch.conf @@ -0,0 +1,15 @@ +[nexus-ebics] +CURRENCY = CHF +BANK_DIALECT = postfinance +HOST_BASE_URL = http://localhost:8080/ebicsweb +BANK_PUBLIC_KEYS_FILE = test/fetch/bank-keys.json +CLIENT_PRIVATE_KEYS_FILE = test/fetch/client-keys.json +IBAN = CH7789144474425692816 +HOST_ID = PFEBICS +USER_ID = PFC00563 +PARTNER_ID = PFC00563 +BIC = BIC +NAME = myname + +[libeufin-nexusdb-postgres] +CONFIG = postgres:///libeufincheck +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt @@ -19,10 +19,12 @@ package tech.libeufin.nexus +import tech.libeufin.common.decodeBase64 import org.w3c.dom.Document import org.w3c.dom.Element import java.io.InputStream import java.io.StringWriter +import java.io.ByteArrayInputStream import java.util.UUID import java.time.LocalDate import java.time.LocalDateTime @@ -178,6 +180,7 @@ class XmlDestructor internal constructor(private val el: Element) { fun uuid(): UUID = UUID.fromString(text()) fun text(): String = el.textContent + fun base64(): ByteArray = el.textContent.decodeBase64() fun bool(): Boolean = el.textContent.toBoolean() fun float(): Float = el.textContent.toFloat() fun date(): LocalDate = LocalDate.parse(text(), DateTimeFormatter.ISO_DATE) @@ -202,12 +205,17 @@ class XmlDestructor internal constructor(private val el: Element) { companion object { - fun <T> fromStream(xml: InputStream, root: String, f: XmlDestructor.() -> T): T { + fun <T> parse(xml: String, root: String, f: XmlDestructor.() -> T): T { + val inputStream = ByteArrayInputStream(xml.toByteArray()) + return parse(inputStream, root, f) + } + + fun <T> parse(xml: InputStream, root: String, f: XmlDestructor.() -> T): T { val doc = XMLUtil.parseIntoDom(xml) - return fromDoc(doc, root, f) + return parse(doc, root, f) } - fun <T> fromDoc(doc: Document, root: String, f: XmlDestructor.() -> T): T { + fun <T> parse(doc: Document, root: String, f: XmlDestructor.() -> T): T { if (doc.documentElement.localName != root) { throw DestructionError("expected root '$root' got '${doc.documentElement.localName}'") } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSetup.kt @@ -328,6 +328,7 @@ class EbicsSetup: CliktCommand() { } catch (e: Exception) { // This can happen if HKD is not supported logger.warn("HKD failed: ${e.fmt()}") + logger.trace("", e) } println("setup ready") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsAdministrative.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsAdministrative.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -76,7 +76,7 @@ object EbicsAdministrative { } fun parseHEV(doc: Document): EbicsResponse<List<VersionNumber>> { - return XmlDestructor.fromDoc(doc, "ebicsHEVResponse") { + return XmlDestructor.parse(doc, "ebicsHEVResponse") { val technicalCode = one("SystemReturnCode") { EbicsReturnCode.lookup(one("ReturnCode").text()) } @@ -109,7 +109,7 @@ object EbicsAdministrative { ebicsOrder(type) } ?: EbicsOrder.V3(type) } - return XmlDestructor.fromStream(stream, "HKDResponseOrderData") { + return XmlDestructor.parse(stream, "HKDResponseOrderData") { val partnerInfo = one("PartnerInfo") { val name = one("AddressInfo").opt("Name")?.text() val accounts = map("AccountInfo") { @@ -155,7 +155,7 @@ object EbicsAdministrative { } fun parseHAA(stream: InputStream): HAA { - return XmlDestructor.fromStream(stream, "HAAResponseOrderData") { + return XmlDestructor.parse(stream, "HAAResponseOrderData") { val orders = map("Service") { ebicsOrder("BTD") } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -290,7 +290,7 @@ class EbicsBTS( companion object { fun parseResponse(doc: Document): EbicsResponse<BTSResponse> { - return XmlDestructor.fromDoc(doc, "ebicsResponse") { + return XmlDestructor.parse(doc, "ebicsResponse") { var transactionID: String? = null var numSegments: Int? = null lateinit var technicalCode: EbicsReturnCode @@ -312,11 +312,11 @@ class EbicsBTS( } one("body") { opt("DataTransfer") { - segment = one("OrderData").text().decodeBase64() + segment = one("OrderData").base64() dataEncryptionInfo = opt("DataEncryptionInfo", signed = true) { DataEncryptionInfo( - one("TransactionKey").text().decodeBase64(), - one("EncryptionPubKeyDigest").text().decodeBase64() + one("TransactionKey").base64(), + one("EncryptionPubKeyDigest").base64() ) } } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -135,7 +135,7 @@ class EbicsKeyMng( companion object { fun parseResponse(doc: Document, clientEncryptionKey: RSAPrivateCrtKey): EbicsResponse<InputStream?> { - return XmlDestructor.fromDoc(doc, "ebicsKeyManagementResponse") { + return XmlDestructor.parse(doc, "ebicsKeyManagementResponse") { lateinit var technicalCode: EbicsReturnCode lateinit var bankCode: EbicsReturnCode var payload: InputStream? = null @@ -149,11 +149,11 @@ class EbicsKeyMng( payload = opt("DataTransfer") { val descriptionInfo = one("DataEncryptionInfo", signed = true) { DataEncryptionInfo( - one("TransactionKey").text().decodeBase64(), - one("EncryptionPubKeyDigest").text().decodeBase64() + one("TransactionKey").base64(), + one("EncryptionPubKeyDigest").base64() ) } - val chunk = one("OrderData").text().decodeBase64() + val chunk = one("OrderData").base64() decryptAndDecompressPayload( clientEncryptionKey, descriptionInfo, @@ -170,21 +170,7 @@ class EbicsKeyMng( } fun parseHpbOrder(data: InputStream): Pair<RSAPublicKey, RSAPublicKey> { - fun XmlDestructor.rsaPubKey(): RSAPublicKey { - val cert = opt("X509Data")?.one("X509Certificate")?.text()?.decodeBase64() - return if (cert != null) { - CryptoUtil.RSAPublicFromCertificate(cert) - } else { - one("PubKeyValue").one("RSAKeyValue") { - CryptoUtil.RSAPublicFromComponents( - one("Modulus").text().decodeBase64(), - one("Exponent").text().decodeBase64(), - ) - } - - } - } - return XmlDestructor.fromStream(data, "HPBResponseOrderData") { + return XmlDestructor.parse(data, "HPBResponseOrderData") { val authPub = one("AuthenticationPubKeyInfo") { val version = one("AuthenticationVersion").text() require(version == "X002") { "Expected authentication version X002 got unsupported $version" } @@ -199,4 +185,18 @@ class EbicsKeyMng( } } } +} + +fun XmlDestructor.rsaPubKey(): RSAPublicKey { + val cert = opt("X509Data")?.one("X509Certificate")?.text()?.decodeBase64() + return if (cert != null) { + CryptoUtil.RSAPublicFromCertificate(cert) + } else { + one("PubKeyValue").one("RSAKeyValue") { + CryptoUtil.RSAPublicFromComponents( + one("Modulus").base64(), + one("Exponent").base64(), + ) + } + } } \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/helpers.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/helpers.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -21,12 +21,18 @@ package tech.libeufin.nexus import io.ktor.client.* import io.ktor.client.plugins.* +import io.ktor.client.engine.mock.MockEngine import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter +/** Use for unit testing */ +var MOCK_ENGINE: MockEngine? = null + /** Create an HTTP client for EBICS requests */ -fun httpClient(): HttpClient = HttpClient { +fun httpClient(): HttpClient = MOCK_ENGINE?.let { + HttpClient(it) +} ?: HttpClient { install(HttpTimeout) { // It can take a lot of time for the bank to generate documents socketTimeoutMillis = 5 * 60 * 1000 diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt @@ -625,7 +625,7 @@ fun parseTx(notifXml: InputStream, dialect: Dialect): List<AccountTransactions> } accountTxs.add(AccountTransactions.fromParts(iban, currency, txInfos)) } - XmlDestructor.fromStream(notifXml, "Document") { + XmlDestructor.parse(notifXml, "Document") { // Camt.053 opt("BkToCstmrStmt")?.each("Stmt") { parseInner() } // Camt.052 diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/hac.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/hac.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -47,7 +47,7 @@ data class CustomerAck( /** Parse HAC pain.002 XML file */ fun parseCustomerAck(xml: InputStream): List<CustomerAck> { - return XmlDestructor.fromStream(xml, "Document") { + return XmlDestructor.parse(xml, "Document") { one("CstmrPmtStsRpt").map("OrgnlPmtInfAndSts") { val actionType = one("OrgnlPmtInfId").enum<HacAction>() one("StsRsnInf") { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/pain002.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/pain002.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -121,7 +121,7 @@ fun parseCustomerPaymentStatusReport(xml: InputStream): MsgStatus { } } - return XmlDestructor.fromStream(xml, "Document") { + return XmlDestructor.parse(xml, "Document") { one("CstmrPmtStsRpt") { val (id, code, reasons) = one("OrgnlGrpInfAndSts") { val id = one("OrgnlMsgId").text() diff --git a/nexus/src/test/kotlin/CliTest.kt b/nexus/src/test/kotlin/CliTest.kt @@ -56,7 +56,7 @@ class CliTest { val cfg = nexusCfg.ebics val clientKeysPath = cfg.clientPrivateKeysPath val bankKeysPath = cfg.bankPublicKeysPath - clientKeysPath.parent!!.createDirectories() + clientKeysPath.parent!!.createParentDirectories() clientKeysPath.parent!!.toFile().setWritable(true) bankKeysPath.parent!!.createDirectories() diff --git a/nexus/src/test/kotlin/EbicsTest.kt b/nexus/src/test/kotlin/EbicsTest.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,18 +17,334 @@ * <http://www.gnu.org/licenses/> */ +import com.github.ajalt.clikt.testing.test import io.ktor.client.engine.mock.* import io.ktor.http.* +import io.ktor.http.content.* import org.junit.Test -import tech.libeufin.nexus.ebics.EbicsError -import tech.libeufin.nexus.ebics.postToBank -import tech.libeufin.nexus.generateKeysPdf -import kotlin.io.path.Path -import kotlin.io.path.writeBytes -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith +import tech.libeufin.nexus.cli.LibeufinNexus +import tech.libeufin.nexus.ebics.* +import tech.libeufin.nexus.* +import tech.libeufin.common.* +import tech.libeufin.common.crypto.CryptoUtil +import kotlin.io.path.* +import kotlin.test.* +import java.security.interfaces.RSAPrivateCrtKey +import java.security.interfaces.RSAPublicKey + + +class EbicsState { + private val bankSignKey: RSAPrivateCrtKey = CryptoUtil.genRSAPrivate(2048) + private val bankEncKey: RSAPrivateCrtKey = CryptoUtil.genRSAPrivate(2048) + private val bankAuthKey: RSAPrivateCrtKey = CryptoUtil.genRSAPrivate(2048) + + private var clientSignPub: RSAPublicKey? = null + private var clientEncrPub: RSAPublicKey? = null + private var clientAuthPub: RSAPublicKey? = null + + private var transactionId: String? = null + + companion object { + private val HEV_OK = XmlBuilder.toBytes("ebicsHEVResponse") { + attr("xmlns", "http://www.ebics.org/H000") + el("SystemReturnCode") { + el("ReturnCode", "000000") + el("ReportText", "[EBICS_OK] OK") + } + el("VersionNumber") { + attr("ProtocolVersion", "H005") + text("03.00") + } + } + private val KEY_OK = XmlBuilder.toBytes("ebicsKeyManagementResponse") { + attr("xmlns", "http://www.ebics.org/H005") + el("header") { + attr("authenticate", "true") + el("mutable") { + el("ReturnCode", "000000") + el("ReportText", "[EBICS_OK] OK") + } + } + el("body") { + el("ReturnCode") { + attr("authenticate", "true") + text("000000") + } + } + } + + private fun parseUnsecureRequest(body: String, order: String, root: String, parse: XmlDestructor.() -> Unit) { + XmlDestructor.parse(body, "ebicsUnsecuredRequest") { + val adminOrder = one("header").one("static").one("OrderDetails").one("AdminOrderType").text() + assertEquals(adminOrder, order) + val chunk = one("body").one("DataTransfer").one("OrderData").base64() + val deflated = chunk.inputStream().inflate() + XmlDestructor.parse(deflated, root) { parse() } + } + } + } + + fun hev(body: String): ByteArray { + // Parse HEV request + val hostId = XmlDestructor.parse(body, "ebicsHEVRequest") { + one("HostID").text() + } + return HEV_OK + } + + fun ini(body: String): ByteArray { + parseUnsecureRequest(body, "INI", "SignaturePubKeyOrderData") { + clientSignPub = one("SignaturePubKeyInfo") { + val version = one("SignatureVersion").text() + assertEquals(version, "A006") + rsaPubKey() + } + } + return KEY_OK + } + + fun hia(body: String): ByteArray { + parseUnsecureRequest(body, "HIA", "HIARequestOrderData") { + clientAuthPub = one("AuthenticationPubKeyInfo") { + val version = one("AuthenticationVersion").text() + assertEquals(version, "X002") + rsaPubKey() + } + clientEncrPub = one("EncryptionPubKeyInfo") { + val version = one("EncryptionVersion").text() + assertEquals(version, "E002") + rsaPubKey() + } + } + return KEY_OK + } + + fun hpb(body: String): ByteArray { + // Parse HPB request + XmlDestructor.parse(body, "ebicsNoPubKeyDigestsRequest") { + val order = one("header").one("static").one("OrderDetails").one("AdminOrderType").text() + assertEquals(order, "HPB") + } + + val payload = XmlBuilder.toBytes("HPBResponseOrderData") { + el("AuthenticationPubKeyInfo") { + el("PubKeyValue") { + el("RSAKeyValue") { + el("Modulus", bankAuthKey.modulus.encodeBase64()) + el("Exponent", bankAuthKey.publicExponent.encodeBase64()) + } + } + el("AuthenticationVersion", "X002") + } + el("EncryptionPubKeyInfo") { + el("PubKeyValue") { + el("RSAKeyValue") { + el("Modulus", bankEncKey.modulus.encodeBase64()) + el("Exponent", bankEncKey.publicExponent.encodeBase64()) + } + } + el("EncryptionVersion", "E002") + } + }.inputStream().deflate() + + val (transactionKey, encryptedTransactionKey) = CryptoUtil.genEbicsE002Key(clientEncrPub!!) + val encrypted = CryptoUtil.encryptEbicsE002(transactionKey, payload) + + return XmlBuilder.toBytes("ebicsKeyManagementResponse") { + attr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") + attr("xmlns", "http://www.ebics.org/H005") + el("header") { + attr("authenticate", "true") + el("mutable") { + el("ReturnCode", "000000") + el("ReportText", "[EBICS_OK] OK") + } + } + el("body") { + el("DataTransfer") { + el("DataEncryptionInfo") { + attr("authenticate", "true") + el("EncryptionPubKeyDigest") { + attr("Version", "E002") + attr("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256") + text(CryptoUtil.getEbicsPublicKeyHash(clientEncrPub!!).encodeBase64()) + } + el("TransactionKey", encryptedTransactionKey.encodeBase64()) + } + el("OrderData", encrypted.encodeBase64()) + } + el("ReturnCode") { + attr("authenticate", "true") + text("000000") + } + } + } + } + + fun receipt(body: String): ByteArray { + XmlDestructor.parse(body, "ebicsRequest") { + one("header") { + val id = one("static").one("TransactionID").text() + assertEquals(id, transactionId) + val phase = one("mutable").one("TransactionPhase").text() + assertEquals(phase, "Receipt") + } + val code = one("body").one("TransferReceipt").one("ReceiptCode").text() + assertEquals(code, "0") + } + val doc = XmlBuilder.toDom("ebicsResponse", "http://www.ebics.org/H005") { + attr("http://www.w3.org/2000/xmlns/", "xmlns", "http://www.ebics.org/H005") + el("header") { + attr("authenticate", "true") + el("static") { + el("TransactionID", transactionId!!) + } + el("mutable") { + el("TransactionPhase", "Receipt") + el("ReturnCode", "000000") + el("ReportText", "[EBICS_OK] OK") + } + } + el("AuthSignature") + el("body") { + el("ReturnCode") { + attr("authenticate", "true") + text("000000") + } + } + } + transactionId = null + XMLUtil.signEbicsDocument(doc, bankAuthKey) + return XMLUtil.convertDomToBytes(doc) + } + + fun hkd(body: String): ByteArray { + XmlDestructor.parse(body, "ebicsRequest") { + one("header") { + val adminOrder = one("static").one("OrderDetails").one("AdminOrderType").text() + assertEquals(adminOrder, "HKD") + val phase = one("mutable").one("TransactionPhase").text() + assertEquals(phase, "Initialisation") + } + } + transactionId = randEbicsId() + val payload = XmlBuilder.toBytes("HKDResponseOrderData") { + el("PartnerInfo") { + el("AddressInfo") + el("OrderInfo") { + el("AdminOrderType", "BTD") + el("Service") { + el("ServiceName", "STM") + el("Scope", "CH") + el("Container") { + attr("containerType", "ZIP") + } + el("MsgName") { + attr("version", "04") + text("camt.052") + } + } + el("Description") + } + } + + }.inputStream().deflate() + + val (transactionKey, encryptedTransactionKey) = CryptoUtil.genEbicsE002Key(clientEncrPub!!) + val encrypted = CryptoUtil.encryptEbicsE002(transactionKey, payload) + val doc = XmlBuilder.toDom("ebicsResponse", "http://www.ebics.org/H005") { + attr("http://www.w3.org/2000/xmlns/", "xmlns", "http://www.ebics.org/H005") + el("header") { + attr("authenticate", "true") + el("static") { + el("TransactionID", transactionId!!) + el("NumSegments", "1") + } + el("mutable") { + el("TransactionPhase", "Initialisation") + el("SegmentNumber") { + attr("lastSegment", "true") + text("1") + } + el("ReturnCode", "000000") + el("ReportText", "[EBICS_OK] OK") + } + } + el("AuthSignature") + el("body") { + el("DataTransfer") { + el("DataEncryptionInfo") { + attr("authenticate", "true") + el("EncryptionPubKeyDigest") { + attr("Version", "E002") + attr("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256") + text(CryptoUtil.getEbicsPublicKeyHash(clientEncrPub!!).encodeBase64()) + } + el("TransactionKey", encryptedTransactionKey.encodeBase64()) + } + el("OrderData", encrypted.encodeBase64()) + } + el("ReturnCode") { + attr("authenticate", "true") + text("000000") + } + } + } + XMLUtil.signEbicsDocument(doc, bankAuthKey) + return XMLUtil.convertDomToBytes(doc) + } + + fun haa(body: String): ByteArray { + XmlDestructor.parse(body, "ebicsRequest") { + one("header") { + val adminOrder = one("static").one("OrderDetails").one("AdminOrderType").text() + assertEquals(adminOrder, "HAA") + val phase = one("mutable").one("TransactionPhase").text() + assertEquals(phase, "Initialisation") + } + } + throw Exception("tmp") + } +} + +@OptIn(kotlin.io.path.ExperimentalPathApi::class) class EbicsTest { + private val nexusCmd = LibeufinNexus() + private val bank = EbicsState() + + private fun setMock(sequences: Sequence<(String) -> ByteArray>) { + val steps = sequences.iterator() + val cfg: MockEngineConfig = MockEngineConfig() + cfg.addHandler { req -> + val body = String((req.body as OutgoingContent.ByteArrayContent).bytes()) + val res = steps.next()(body) + respond(res) + } + MOCK_ENGINE = MockEngine(cfg) + } + + private fun ebicsSetup() { + // Reset current keys + val dir = Path("test/fetch") + dir.deleteRecursively() + dir.createDirectories() + + // Set setup mock + setMock(sequence { + yield(bank::hev) + yield(bank::ini) + yield(bank::hia) + yield(bank::hpb) + yield(bank::hkd) + yield(bank::receipt) + }) + + // Run setup + val res = nexusCmd.test("ebics-setup -L TRACE -c conf/fetch.conf --auto-accept-keys") + assertEquals(res.statusCode, 0) + } + // POSTs an EBICS message to the mock bank. Tests // the main branches: unreachable bank, non-200 status // code, and 200. @@ -70,4 +386,9 @@ class EbicsTest { val pdf = generateKeysPdf(clientKeys, config.ebics) Path("/tmp/libeufin-nexus-test-keys.pdf").writeBytes(pdf) } + + @Test + fun setup() = setup { _, _ -> + ebicsSetup() + } } \ No newline at end of file diff --git a/nexus/src/test/kotlin/XmlCombinatorsTest.kt b/nexus/src/test/kotlin/XmlCombinatorsTest.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -91,7 +91,7 @@ class XmlCombinatorsTest { text("not signed 2") } } - XmlDestructor.fromDoc(trapped, "document") { + XmlDestructor.parse(trapped, "document") { assertEquals(3, map("order") { text() }.size) one("order", signed = true) { assertEquals("signed", text()) diff --git a/testbench/src/test/kotlin/IntegrationTest.kt b/testbench/src/test/kotlin/IntegrationTest.kt @@ -39,8 +39,6 @@ import tech.libeufin.nexus.* import tech.libeufin.nexus.cli.LibeufinNexus import tech.libeufin.nexus.cli.registerIncomingPayment import tech.libeufin.nexus.iso20022.* -import tech.libeufin.nexus.nexusConfig -import tech.libeufin.nexus.withDb import java.time.Instant import kotlin.io.path.Path import kotlin.io.path.readText