libeufin

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

commit 90db87fff2b98a1c2cfa7ec8ed6e353fbf288c5d
parent e6cc1fc96cd2b431f58abc735cb04f01e9123f8e
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Sat,  7 Dec 2019 03:10:00 +0100

Implementing /history API at Sandbox.

Diffstat:
Mnexus/src/main/kotlin/JSON.kt | 9+--------
Mnexus/src/main/kotlin/Main.kt | 10++++++----
Msandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt | 7+++++--
Msandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt | 16++++++++++++++++
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 39+++++++++++++++++++++++++++++++++++++--
Msandbox/src/main/python/libeufin-cli | 22+++++++++++++++++++++-
Msandbox/src/test/kotlin/DbTest.kt | 30++++++++++++++++++++++++++++++
7 files changed, 116 insertions(+), 17 deletions(-)

diff --git a/nexus/src/main/kotlin/JSON.kt b/nexus/src/main/kotlin/JSON.kt @@ -2,7 +2,7 @@ package tech.libeufin.nexus import com.google.gson.annotations.JsonAdapter import com.squareup.moshi.JsonClass - +import org.joda.time.DateTime data class EbicsBackupRequest( @@ -57,11 +57,4 @@ data class EbicsSubscriberInfoResponse( */ data class EbicsSubscribersResponse( val ebicsSubscribers: List<EbicsSubscriberInfoResponse> -) - -/** - * Error message. - */ -data class NexusError( - val message: String ) \ No newline at end of file diff --git a/nexus/src/main/kotlin/Main.kt b/nexus/src/main/kotlin/Main.kt @@ -41,6 +41,7 @@ import io.ktor.server.netty.Netty import org.apache.xml.security.binding.xmldsig.RSAKeyValueType import org.apache.xml.security.binding.xmldsig.SignatureType import org.jetbrains.exposed.sql.transactions.transaction +import org.joda.time.DateTime import org.slf4j.LoggerFactory import tech.libeufin.sandbox.* import tech.libeufin.schema.ebics_h004.* @@ -197,11 +198,12 @@ fun main() { val id = expectId(call.parameters["id"]) val body = call.receive<EbicsDateRange>() - val startDate = LocalDate.parse(body.start) - val endDate = LocalDate.parse(body.end) + val startDate = DateTime.parse(body.start) + val endDate = DateTime.parse(body.end) // will throw DateTimeParseException if strings are malformed. + val subscriberData = transaction { containerInit(EbicsSubscriberEntity.findById(id) ?: throw SubscriberNotFoundError(HttpStatusCode.NotFound)) } @@ -213,8 +215,8 @@ fun main() { "C52", getNonce(128), getGregorianDate(), - getGregorianDate(startDate.year, startDate.monthValue, startDate.dayOfMonth), - getGregorianDate(endDate.year, endDate.monthValue, endDate.dayOfMonth) + getGregorianDate(startDate.year, startDate.monthOfYear, startDate.dayOfMonth), + getGregorianDate(endDate.year, endDate.monthOfYear, endDate.dayOfMonth) ), subscriberData.customerAuthPriv ) diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt @@ -112,6 +112,7 @@ open class IntIdTableWithAmount : IntIdTable() { override fun valueFromDB(value: Any): Any { val valueFromDB = super.valueFromDB(value) + try { return when (valueFromDB) { is BigDecimal -> valueFromDB.setScale(SCALE_TWO, RoundingMode.UNNECESSARY) @@ -128,11 +129,9 @@ open class IntIdTableWithAmount : IntIdTable() { e.printStackTrace() throw BadAmount(value) } - } override fun valueToDB(value: Any?): Any? { - try { (value as BigDecimal).setScale(SCALE_TWO, RoundingMode.UNNECESSARY) } catch (e: Exception) { @@ -140,6 +139,10 @@ open class IntIdTableWithAmount : IntIdTable() { throw BadAmount(value) } + if ((value as BigDecimal).compareTo(BigDecimal.ZERO) == 0) { + logger.error("Cannot have transactions of ZERO amount") + throw BadAmount(value) + } return super.valueToDB(value) } } diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt @@ -54,6 +54,22 @@ data class CustomerEbicsInfo( val userId: String ) +data class CustomerHistoryRequest( + val start: String, + val end: String +) + +data class CustomerHistoryResponseElement( + var amount: String, + val subject: String, + val counterpart: String, + val date: String +) + +data class CustomerHistoryResponse( + var history: MutableList<CustomerHistoryResponseElement> = mutableListOf() +) + /** * Wrapper type around initialization letters * for RSA keys. diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -38,7 +38,10 @@ import io.ktor.routing.post import io.ktor.routing.routing import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty +import org.jetbrains.exposed.sql.GreaterEqOp +import org.jetbrains.exposed.sql.LessEqOp import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.dateTimeParam import org.jetbrains.exposed.sql.transactions.transaction import org.joda.time.DateTime import org.slf4j.Logger @@ -152,10 +155,10 @@ fun main() { bankCustomer = customerEntity } - for (i in 1..10) { + for (i in IntRange(0, 4) + IntRange(6, 10)) { BankTransactionEntity.new { counterpart = "IBAN" - amount = Amount(1) + amount = Amount(5 - i) subject = "transaction $i" date = DateTime.now() localCustomer = customerEntity @@ -191,6 +194,38 @@ fun main() { } routing { + post("/{id}/history") { + + logger.debug("/history fired up") + + val req = call.receive<CustomerHistoryRequest>() + val startDate = DateTime.parse(req.start) + val endDate = DateTime.parse(req.end) + + logger.debug("Fetching history from ${startDate.toString()}, to ${endDate.toString()}") + + val ret = CustomerHistoryResponse() + + transaction { + + BankTransactionEntity.find { + BankTransactionsTable.date.between(startDate, endDate) + }.forEach { + ret.history.add( + CustomerHistoryResponseElement( + subject = it.subject, + amount = "${it.amount.toString()} EUR", + counterpart = it.counterpart, + date = it.date.toString("Y-M-d") + ) + ) + } + } + + call.respond(ret) + return@post + } + get("/{id}/balance") { val (name, balance) = transaction { val tmp: BankCustomerEntity = findCustomer(call.parameters["id"]) diff --git a/sandbox/src/main/python/libeufin-cli b/sandbox/src/main/python/libeufin-cli @@ -280,7 +280,7 @@ def new(obj, user_id, partner_id, system_id, host_id, ebics_url): partner_id = "PARTNER{}".format(salt) url = urljoin(obj["base_url"], "/ebics/subscribers") - body = json=dict( + body = dict( ebicsURL=obj["base_url"], userID=user_id, partnerID=partner_id, @@ -298,6 +298,26 @@ def new(obj, user_id, partner_id, system_id, host_id, ebics_url): print(resp.content.decode("utf-8")) +@nonebics.command(help="Ask the list of transactions related to one account") +@click.pass_obj +@click.option( + "--user-id", + help="ID of the bank customer (no EBICS correlation implied/needed)" , + required=False, + default=1 +) +def history(obj, user_id): + + url = urljoin(obj["bank_base_url"], f"/{user_id}/history") + print(url) + try: + resp = post(url, json=dict(start="2000-01-01", end="2999-12-31")) + except Exception: + print("Could not reach the bank") + return + + print(resp.content.decode("utf-8")) + @nonebics.command(help="Ask the balance for a given customer of the bank") @click.pass_obj diff --git a/sandbox/src/test/kotlin/DbTest.kt b/sandbox/src/test/kotlin/DbTest.kt @@ -14,6 +14,7 @@ import java.math.BigInteger import java.math.MathContext import java.math.RoundingMode import kotlin.math.abs +import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertTrue @@ -87,4 +88,33 @@ class DbTest { } } } + + @Test + fun timeBasedQuery() { + + + val NQUERIES = 10 + + transaction { + + for (i in 1..NQUERIES) { + BankTransactionEntity.new { + amount = Amount("1") + counterpart = "IBAN" + subject = "Salary" + date = DateTime.now() + localCustomer = BankCustomerEntity.new { + name = "employee" + } + } + } + + assertEquals( + NQUERIES, + BankTransactionEntity.find { + BankTransactionsTable.date.between(DateTime.parse("1970-01-01"), DateTime.parse("2999-12-31")) + }.count() + ) + } + } } \ No newline at end of file