libeufin

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

commit 245b3ea998a9e095c4ca685a019364afb8b9f06a
parent 98ab97b89a4b7f56efe1ecbff61bb29c118fa675
Author: MS <ms@taler.net>
Date:   Tue, 30 Jun 2020 22:17:43 +0200

testing account import

and fixing the updating of rows via the sql dsl.
In particular, it is not possible to state:

row[column] = value

but a "update()" method should be invoked on the
involved table.

Diffstat:
Aintegration-tests/test-bankAccount.py | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 1+
Mnexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt | 20++++++++++++++------
Mnexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt | 4+++-
Mnexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt | 17+++++++++--------
5 files changed, 199 insertions(+), 15 deletions(-)

diff --git a/integration-tests/test-bankAccount.py b/integration-tests/test-bankAccount.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +from requests import post, get +from time import sleep +import os +import hashlib +import base64 + +from util import startNexus, startSandbox + +# Nexus user details +USERNAME = "person" +PASSWORD = "y" +USER_AUTHORIZATION_HEADER = "basic {}".format( + base64.b64encode(b"person:y").decode("utf-8") +) + +# Admin authentication +ADMIN_AUTHORIZATION_HEADER = "basic {}".format( + base64.b64encode(b"admin:x").decode("utf-8") +) + +# EBICS details +EBICS_URL = "http://localhost:5000/ebicsweb" +HOST_ID = "HOST01" +PARTNER_ID = "PARTNER1" +USER_ID = "USER1" +EBICS_VERSION = "H004" + +# Subscriber's bank account +SUBSCRIBER_IBAN = "GB33BUKB20201555555555" +SUBSCRIBER_BIC = "BUKBGB22" +SUBSCRIBER_NAME = "Oliver Smith" +BANK_ACCOUNT_LABEL = "savings" + +# Databases +NEXUS_DB = "test-nexus.sqlite3" + + +def fail(msg): + print(msg) + exit(1) + + +def assertResponse(response): + if response.status_code != 200: + print("Test failed on URL: {}".format(response.url)) + # stdout/stderr from both services is A LOT of text. + # Confusing to dump all that to console. + print("Check nexus.log and sandbox.log, probably under /tmp") + exit(1) + # Allows for finer grained checks. + return response + + +startNexus(NEXUS_DB) +startSandbox() + +# make ebics host at sandbox +assertResponse( + post( + "http://localhost:5000/admin/ebics/host", + json=dict(hostID=HOST_ID, ebicsVersion=EBICS_VERSION), + ) +) + +# make new ebics subscriber at sandbox +assertResponse( + post( + "http://localhost:5000/admin/ebics/subscribers", + json=dict(hostID=HOST_ID, partnerID=PARTNER_ID, userID=USER_ID), + ) +) + +# give a bank account to such subscriber, at sandbox +assertResponse( + post( + "http://localhost:5000/admin/ebics/bank-accounts", + json=dict( + subscriber=dict(hostID=HOST_ID, partnerID=PARTNER_ID, userID=USER_ID), + iban=SUBSCRIBER_IBAN, + bic=SUBSCRIBER_BIC, + name=SUBSCRIBER_NAME, + label=BANK_ACCOUNT_LABEL, + ), + ) +) + +# make a new nexus user. +assertResponse( + post( + "http://localhost:5001/users", + headers=dict(Authorization=ADMIN_AUTHORIZATION_HEADER), + json=dict(username=USERNAME, password=PASSWORD), + ) +) + +print("creating bank connection") + +# make a ebics bank connection for the new user. +assertResponse( + post( + "http://localhost:5001/bank-connections", + json=dict( + name="my-ebics", + source="new", + type="ebics", + data=dict( + ebicsURL=EBICS_URL, hostID=HOST_ID, partnerID=PARTNER_ID, userID=USER_ID + ), + ), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) + +assertResponse( + post( + "http://localhost:5001/bank-connections/my-ebics/connect", + json=dict(), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) + +## download list of offered accounts + +assertResponse( + post( + "http://localhost:5001/bank-connections/my-ebics/fetch-accounts", + headers=dict(Authorization=USER_AUTHORIZATION_HEADER) + ) +) + +## show such accounts +listOfferedAccounts = assertResponse( + get( + "http://localhost:5001/bank-connections/my-ebics/accounts", + headers=dict(Authorization=USER_AUTHORIZATION_HEADER) + ) +) + +listOfferedAccountsBefore = assertResponse( + get( + "http://localhost:5001/bank-connections/my-ebics/accounts", + headers=dict(Authorization=USER_AUTHORIZATION_HEADER) + ) +) + +## import one + +assertResponse( + post( + "http://localhost:5001/bank-connections/my-ebics/import-account", + json=dict(offeredAccountId=BANK_ACCOUNT_LABEL, nexusBankAccountId="savings-at-nexus!"), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER) + ) +) + +## make sure the imported account shows up + +listOfferedAccountsAfter = assertResponse( + get( + "http://localhost:5001/bank-connections/my-ebics/accounts", + headers=dict(Authorization=USER_AUTHORIZATION_HEADER) + ) +) + +for el in listOfferedAccountsAfter.json().get("accounts"): + if el.get("nexusBankAccountId") == "savings-at-nexus": + exit(0) + print("Test passed!") + +exit(1) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -216,6 +216,7 @@ object OfferedBankAccountsTable : Table() { val iban = text("iban") val bankCode = text("bankCode") val accountHolder = text("holderName") + // column below gets defined only WHEN the user imports the bank account. val imported = reference("imported", NexusBankAccountsTable).nullable() override val primaryKey = PrimaryKey(offeredAccountId, bankConnection) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt @@ -24,13 +24,12 @@ import io.ktor.application.ApplicationCall import io.ktor.application.call import io.ktor.client.HttpClient import io.ktor.http.HttpStatusCode -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction import org.w3c.dom.Document import tech.libeufin.nexus.* import tech.libeufin.nexus.OfferedBankAccountsTable.iban +import tech.libeufin.nexus.OfferedBankAccountsTable.imported import tech.libeufin.nexus.ebics.fetchEbicsBySpec import tech.libeufin.nexus.ebics.submitEbicsPaymentInitiation import tech.libeufin.nexus.server.FetchSpecJson @@ -306,12 +305,14 @@ suspend fun fetchBankAccountTransactions( fun importBankAccount(call: ApplicationCall, offeredBankAccountId: String, nexusBankAccountId: String) { transaction { + addLogger(StdOutSqlLogger) val conn = requireBankConnection(call, "connid") // first get handle of the offered bank account val offeredAccount = OfferedBankAccountsTable.select { - OfferedBankAccountsTable.offeredAccountId eq offeredBankAccountId + OfferedBankAccountsTable.offeredAccountId eq offeredBankAccountId and + (OfferedBankAccountsTable.bankConnection eq conn.id.value) }.firstOrNull() ?: throw NexusError( - HttpStatusCode.NotFound, "Could not find raw bank account '${offeredBankAccountId}'" + HttpStatusCode.NotFound, "Could not find offered bank account '${offeredBankAccountId}'" ) // detect name collisions first. NexusBankAccountEntity.findById(nexusBankAccountId).run { @@ -334,10 +335,17 @@ fun importBankAccount(call: ApplicationCall, offeredBankAccountId: String, nexus highestSeenBankMessageId = 0 accountHolder = offeredAccount[OfferedBankAccountsTable.accountHolder] } - offeredAccount[OfferedBankAccountsTable.imported] = newImportedAccount + logger.info("Account ${newImportedAccount.id} gets imported") newImportedAccount } } + // importedAccount could be now-or-earlier imported. + OfferedBankAccountsTable.update( + {OfferedBankAccountsTable.offeredAccountId eq offeredBankAccountId and + (OfferedBankAccountsTable.bankConnection eq conn.id.value) } + ) { + it[imported] = importedAccount.id + } } } } \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt @@ -282,7 +282,9 @@ data class OfferedBankAccount( var nexusBankAccountId: String? ) - +data class OfferedBankAccounts( + val accounts: MutableList<OfferedBankAccount> = mutableListOf() +) /** Response type of "GET /bank-accounts" */ data class BankAccounts( diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt @@ -48,6 +48,7 @@ import io.ktor.server.netty.Netty import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.jvm.javaio.toByteReadChannel import io.ktor.utils.io.jvm.javaio.toInputStream +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction @@ -803,19 +804,19 @@ fun serverMain(dbName: String, host: String) { // show all the offered accounts (both imported and non) get("/accounts") { - val ret = mutableListOf<OfferedBankAccount>() + val ret = OfferedBankAccounts() transaction { val conn = requireBankConnection(call, "connid") OfferedBankAccountsTable.select { OfferedBankAccountsTable.bankConnection eq conn.id.value - }.forEach {resultRow -> - ret.add( + }.forEach {offeredAccountRow -> + ret.accounts.add( OfferedBankAccount( - ownerName = resultRow[accountHolder], - iban = resultRow[iban], - bic = resultRow[bankCode], - offeredAccountId = resultRow[offeredAccountId], - nexusBankAccountId = resultRow[imported]?.value // is 'value' the id? + ownerName = offeredAccountRow[accountHolder], + iban = offeredAccountRow[iban], + bic = offeredAccountRow[bankCode], + offeredAccountId = offeredAccountRow[offeredAccountId], + nexusBankAccountId = offeredAccountRow[imported]?.value ) ) }