libeufin

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

commit 2b240cde465de4c6cac727f247c549076cfad1f2
parent 85d10103407b0551d4c39896a71ed97458f2ce2c
Author: MS <ms@taler.net>
Date:   Thu,  4 Jun 2020 19:04:11 +0200

Adapt local integration tests to latest changes.

Diffstat:
Mintegration-tests/test-loopback-highlevel.py | 8++++++--
Mintegration-tests/test-sandbox.py | 7++++++-
Mnexus/build.gradle | 4+---
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 11++++++-----
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 52+++++++++++++++++++++++++++++-----------------------
Anexus/src/test/kotlin/DBTest.kt | 42++++++++++++++++++++++++++++++++++++++++++
Msandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt | 5++---
Msandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt | 32++++++++++++++++++--------------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 4+++-
Mutil/src/main/kotlin/JSON.kt | 1+
10 files changed, 114 insertions(+), 52 deletions(-)

diff --git a/integration-tests/test-loopback-highlevel.py b/integration-tests/test-loopback-highlevel.py @@ -34,7 +34,6 @@ NEXUS_DB="test-nexus.sqlite3" def fail(msg): print(msg) nexus.terminate() - sandbox.terminate() exit(1) @@ -56,7 +55,6 @@ def assertResponse(response): # Confusing to dump all that to console. print("Check nexus.log and sandbox.log, probably under /tmp") nexus.terminate() - sandbox.terminate() exit(1) # Allows for finer grained checks. return response @@ -111,6 +109,12 @@ assertResponse( name="my-loopback", source="new", type="loopback", + data=dict( + iban="myIBAN", + bic="myBIC", + holder="Account Holder Name", + account="my-bank-account" + ) ), headers=dict(Authorization=USER_AUTHORIZATION_HEADER), ) diff --git a/integration-tests/test-sandbox.py b/integration-tests/test-sandbox.py @@ -109,8 +109,13 @@ for i in range(1, 3): "http://localhost:5000/admin/payments", json=dict( creditorIban="ES9121000418450200051332", + creditorBic="BIC", + creditorName="Creditor Name", debitorIban="GB33BUKB20201555555555", - amount="EUR:0.99", + debitorBic="BIC", + debitorName="Debitor Name", + amount="0.99", + currency="EUR", subject="test service #{}".format(i) ) ) diff --git a/nexus/build.gradle b/nexus/build.gradle @@ -66,12 +66,10 @@ dependencies { implementation "org.glassfish.jaxb:jaxb-runtime:2.3.1" implementation 'org.apache.santuario:xmlsec:2.1.4' implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.20' - implementation("com.github.ajalt:clikt:2.7.0") - implementation project(":util") - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' + implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' // Exposed, an SQL library implementation "org.jetbrains.exposed:exposed-core:$exposed_version" diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -202,7 +202,6 @@ object NexusBankAccountsTable : IdTable<String>() { class NexusBankAccountEntity(id: EntityID<String>) : Entity<String>(id) { companion object : EntityClass<String, NexusBankAccountEntity>(NexusBankAccountsTable) - var accountHolder by NexusBankAccountsTable.accountHolder var iban by NexusBankAccountsTable.iban var bankCode by NexusBankAccountsTable.bankCode @@ -245,14 +244,14 @@ class EbicsSubscriberEntity(id: EntityID<Int>) : IntEntity(id) { } object NexusUsersTable : IdTable<String>() { - override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey() + override val id = varchar("id", ID_MAX_LENGTH).entityId() + override val primaryKey = PrimaryKey(id, name = "id") val passwordHash = text("password") val superuser = bool("superuser") } class NexusUserEntity(id: EntityID<String>) : Entity<String>(id) { companion object : EntityClass<String, NexusUserEntity>(NexusUsersTable) - var passwordHash by NexusUsersTable.passwordHash var superuser by NexusUsersTable.superuser } @@ -270,7 +269,8 @@ class NexusBankConnectionEntity(id: EntityID<String>) : Entity<String>(id) { } object FacadesTable : IdTable<String>() { - override val id = FacadesTable.text("id").entityId().primaryKey() + override val id = FacadesTable.text("id").entityId() + override val primaryKey = PrimaryKey(id, name = "id") val type = text("type") val creator = reference("creator", NexusUsersTable) val config = reference("config", TalerFacadeConfigsTable) // see #6266 @@ -317,7 +317,8 @@ fun dbCreateTables(dbName: String) { TalerRequestedPayments, NexusBankConnectionsTable, NexusBankMessagesTable, - FacadesTable + FacadesTable, + TalerFacadeConfigsTable ) } } \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -277,21 +277,21 @@ fun schedulePeriodicWork() { /** Crawls all the facades, and requests history for each of its creators. */ suspend fun downloadFacadesTransactions(myScope: CoroutineScope) { val httpClient = HttpClient() + val work = mutableListOf<Pair<String, String>>() transaction { FacadeEntity.all().forEach { - logger.debug( - "Fetching history for facade: ${it.id.value}, bank account: ${it.config.bankAccount}" - ) - runBlocking { - fetchTransactionsInternal( - httpClient, - it.creator, - it.config.bankAccount, - CollectedTransaction(null, null, null) - ) - } + logger.debug("Fetching history for facade: ${it.id.value}, bank account: ${it.config.bankAccount}") + work.add(Pair(it.creator.id.value, it.config.bankAccount)) } } + work.forEach { + fetchTransactionsInternal( + client = httpClient, + userId = it.first, + accountid = it.second, + ct = CollectedTransaction(null, null, null) + ) + } } fun <T>expectNonNull(param: T?): T { @@ -308,7 +308,7 @@ fun ApplicationCall.expectUrlParameter(name: String): String { suspend fun fetchTransactionsInternal( client: HttpClient, - user: NexusUserEntity, + userId: String, accountid: String, ct: CollectedTransaction ) { @@ -327,11 +327,10 @@ suspend fun fetchTransactionsInternal( "No default bank connection (explicit connection not yet supported)" ) } - val subscriberDetails = getEbicsSubscriberDetails(user.id.value, conn.id.value) + val subscriberDetails = getEbicsSubscriberDetails(userId, conn.id.value) return@transaction object { val connectionType = conn.type val connectionName = conn.id.value - val userId = user.id.value val subscriberDetails = subscriberDetails } } @@ -662,7 +661,7 @@ fun serverMain(dbName: String) { } fetchTransactionsInternal( client, - user, + user.id.value, accountid, ct ) @@ -868,7 +867,6 @@ fun serverMain(dbName: String) { false } } - val hpbData = try { doEbicsHpbRequest(client, subscriber) } catch (e: EbicsProtocolError) { @@ -1097,17 +1095,25 @@ fun serverMain(dbName: String) { } post("/facades") { val body = call.receive<FacadeInfo>() - transaction { + val (user, talerConfig) = transaction { val user = authenticateRequest(call.request) + val talerConfig = TalerFacadeConfigEntity.new { + bankAccount = body.config.bankAccount + bankConnection = body.config.bankConnection + intervalIncrement = body.config.intervalIncremental + reserveTransferLevel = body.config.reserveTransferLevel + } + Pair(user, talerConfig) + } + // Kotlin+Exposed did NOT like the referenced and referencing + // tables to be created inside the same transfer block. This + // problem must be further investigated. + transaction { FacadeEntity.new(body.name) { type = body.type creator = user - config = TalerFacadeConfigEntity.new { - bankAccount = body.config.bankAccount - bankConnection = body.config.bankConnection - intervalIncrement = body.config.intervalIncremental - reserveTransferLevel = body.config.reserveTransferLevel - } + config = talerConfig + highestSeenMsgID = 0 } } call.respondText("Facade created") diff --git a/nexus/src/test/kotlin/DBTest.kt b/nexus/src/test/kotlin/DBTest.kt @@ -0,0 +1,41 @@ +package tech.libeufin.nexus + +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.StdOutSqlLogger +import org.jetbrains.exposed.sql.addLogger +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.Test + +class DBTest { + @Test + fun facadeConfigTest() { + Database.connect("jdbc:sqlite:on-the-fly-db.sqlite3", "org.sqlite.JDBC") + val talerConfig = transaction { + addLogger(StdOutSqlLogger) + SchemaUtils.create( + FacadesTable, + TalerFacadeConfigsTable, + NexusUsersTable + ) + TalerFacadeConfigEntity.new { + bankAccount = "b" + bankConnection = "b" + reserveTransferLevel = "any" + intervalIncrement = "any" + } + } + transaction { + val user = NexusUserEntity.new("u") { + passwordHash = "x" + superuser = true + } + FacadeEntity.new("my-id") { + type = "any" + creator = user + config = talerConfig + highestSeenMsgID = 0 + } + } + } +} +\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt @@ -258,6 +258,7 @@ object PaymentsTable : IntIdTable() { val debitorName = text("debitorName") val subject = text("subject") val amount = text("amount") + val currency = text("currency") val date = long("date") } @@ -272,10 +273,8 @@ class PaymentEntity(id: EntityID<Int>) : IntEntity(id) { var debitorName by PaymentsTable.debitorName var subject by PaymentsTable.subject var amount by PaymentsTable.amount - - /** in the CURRENCY:X.Y format */ + var currency by PaymentsTable.currency var date by PaymentsTable.date - /** Date when the payment was persisted in this system. */ } /** diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -158,7 +158,6 @@ fun buildCamtString(type: Int, subscriberIban: String, history: MutableList<RawP val dashedDate = expectNonNull(it.date) val now = LocalDateTime.now() val zonedDateTime = now.toZonedString() - val amount = parseAmount(it.amount) ret.add( constructXml(indent = true) { root("Document") { @@ -268,8 +267,8 @@ fun buildCamtString(type: Int, subscriberIban: String, history: MutableList<RawP } element("Ntry") { element("Amt") { - attribute("Ccy", amount.currency) - text(amount.amount.toString()) + attribute("Ccy", it.currency) + text(it.amount) } element("CdtDbtInd") { text( @@ -421,7 +420,7 @@ private fun constructCamtResponse( PaymentsTable.creditorIban eq bankAccount.iban or (PaymentsTable.debitorIban eq bankAccount.iban) /** - FIXME! + FIXME: add the following condition too: and (PaymentsTable.date.between(start.millis, end.millis)) */ }.forEach { @@ -435,7 +434,8 @@ private fun constructCamtResponse( debitorBic = it.debitorBic, debitorName = it.debitorName, date = importDateFromMillis(it.date).toDashedDate(), - amount = it.amount + amount = it.amount, + currency = it.currency ) ) } @@ -455,26 +455,33 @@ private fun handleEbicsPTK(requestContext: RequestContext): ByteArray { /** * Process a payment request in the pain.001 format. */ -private fun handleCct(paymentRequest: String) { +private fun handleCct(paymentRequest: String, initiatorName: String) { /** * NOTE: this function is ONLY required to store some details * to put then in the camt report. IBANs / amount / subject / names? */ val painDoc = XMLUtil.parseStringIntoDom(paymentRequest) val creditorIban = painDoc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']") + val creditorBic = painDoc.pickString("//*[local-name()='CdtrAgt']//*[local-name()='BIC']") + val creditorName = painDoc.pickString("//*[local-name()='Cdtr']//*[local-name()='Nm']") val debitorIban = painDoc.pickString("//*[local-name()='DbtrAcct']//*[local-name()='IBAN']") + val debitorBic = painDoc.pickString("//*[local-name()='DbtrAgt']//*[local-name()='BIC']") + val debitorName = initiatorName val subject = painDoc.pickString("//*[local-name()='Ustrd']") val amount = painDoc.pickString("//*[local-name()='InstdAmt']") + val currency = painDoc.pickString("//*[local-name()='InstdAmt']/@Ccy") transaction { PaymentEntity.new { this.creditorIban = creditorIban + this.creditorBic = creditorBic + this.creditorName = creditorName this.debitorIban = debitorIban + this.debitorBic = debitorBic + this.debitorName = debitorName this.subject = subject this.amount = amount - /** For now, the date discards any - * information about hours and minor units of time. - */ + this.currency = currency this.date = Instant.now().toEpochMilli() } } @@ -915,10 +922,10 @@ private fun handleEbicsUploadTransactionTransmission(requestContext: RequestCont throw NotImplementedError() } } - if (getOrderTypeFromTransactionId(requestTransactionID) == "CCT") { logger.debug("Attempting a payment.") - handleCct(unzippedData.toString(Charsets.UTF_8)) + val involvedBankAccout = getBankAccountFromSubscriber(requestContext.subscriber) + handleCct(unzippedData.toString(Charsets.UTF_8), involvedBankAccout.name) } return EbicsResponse.createForUploadTransferPhase( requestTransactionID, @@ -1037,15 +1044,12 @@ suspend fun ApplicationCall.ebicsweb() { val responseXmlStr = transaction { // Step 1 of 3: Get information about the host and subscriber - val requestContext = makeReqestContext(requestObject) - // Step 2 of 3: Validate the signature val verifyResult = XMLUtil.verifyEbicsDocument(requestDocument, requestContext.clientAuthPub) if (!verifyResult) { throw EbicsInvalidRequestError() } - // Step 3 of 3: Generate response val ebicsResponse: EbicsResponse = when (requestObject.header.mutable.transactionPhase) { EbicsTypes.TransactionPhaseType.INITIALISATION -> { diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -175,7 +175,8 @@ fun main() { creditorBic = it.creditorBic, creditorName = it.creditorName, debitorBic = it.debitorBic, - debitorName = it.debitorName + debitorName = it.debitorName, + currency = it.currency ) ) } @@ -199,6 +200,7 @@ fun main() { debitorName = body.debitorName subject = body.subject amount = body.amount + currency = body.currency date = Instant.now().toEpochMilli() } } diff --git a/util/src/main/kotlin/JSON.kt b/util/src/main/kotlin/JSON.kt @@ -13,6 +13,7 @@ data class RawPayment( val debitorBic: String, val debitorName: String, val amount: String, + val currency: String, val subject: String, val date: String? ) \ No newline at end of file