libeufin

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

commit c643f732b6ecbfb0b8457b66bb98fed79f346a84
parent f632635b3292df19b5c204861eab7f74be07731b
Author: MS <ms@taler.net>
Date:   Tue, 19 Sep 2023 09:01:15 +0200

Testing GET /transactions

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/Main.kt | 17++++++++++++++---
Mbank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt | 8++++++--
Mbank/src/test/kotlin/DatabaseTest.kt | 49++++++++++++++++++++++++++++++++-----------------
Mbank/src/test/kotlin/LibeuFinApiTest.kt | 33+++++++++++++++++++++++++++++++++
4 files changed, 85 insertions(+), 22 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -140,6 +140,7 @@ val webApp: Application.() -> Unit = { install(IgnoreTrailingSlash) install(ContentNegotiation) { json(Json { + prettyPrint = true ignoreUnknownKeys = true // Registering custom parser for RelativeTime serializersModule = SerializersModule { @@ -159,11 +160,21 @@ val webApp: Application.() -> Unit = { * format. */ exception<BadRequestException> {call, cause -> - // Discouraged use, but the only helpful message: + /** + * NOTE: extracting the root cause helps with JSON error messages, + * because they mention the particular way they are invalid, but OTOH + * it loses (by getting null) other error messages, like for example + * the one from MissingRequestParameterException. Therefore, in order + * to get the most detailed message, we must consider BOTH sides: + * the 'cause' AND its root cause! + */ var rootCause: Throwable? = cause.cause while (rootCause?.cause != null) rootCause = rootCause.cause - logger.error(rootCause?.message) + /* Here getting _some_ error message, by giving precedence + * to the root cause, as otherwise JSON details would be lost. */ + val errorMessage: String? = rootCause?.message ?: cause.message + logger.error(errorMessage) // Telling apart invalid JSON vs missing parameter vs invalid parameter. val talerErrorCode = when(cause) { is MissingRequestParameterException -> @@ -176,7 +187,7 @@ val webApp: Application.() -> Unit = { status = HttpStatusCode.BadRequest, message = TalerError( code = talerErrorCode.code, - hint = rootCause?.message + hint = errorMessage )) } /** diff --git a/bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt b/bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt @@ -17,7 +17,7 @@ fun Routing.transactionsHandlers() { val resourceName = call.expectUriComponent("USERNAME") if (c.login != resourceName && c.login != "admin") throw forbidden() // Collecting params. - val deltaParam: String = call.request.queryParameters["delta"] ?: throw MissingRequestParameterException("Parameter 'delta' not found") + val deltaParam: String = call.request.queryParameters["delta"] ?: throw MissingRequestParameterException(parameterName = "delta") val delta: Long = try { deltaParam.toLong() } catch (e: Exception) { @@ -40,7 +40,11 @@ fun Routing.transactionsHandlers() { ?: throw internalServerError("Customer '${c.login}' lacks bank account.") val bankAccountId = bankAccount.bankAccountId ?: throw internalServerError("Bank account lacks row ID.") - val history: List<BankAccountTransaction> = db.bankTransactionGetHistory(bankAccountId, start, delta) + val history: List<BankAccountTransaction> = db.bankTransactionGetHistory( + start = start, + delta = delta, + bankAccountId = bankAccountId + ) val res = BankAccountTransactionsResponse(transactions = mutableListOf()) history.forEach { res.transactions.add(BankAccountTransactionInfo( diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt @@ -24,6 +24,19 @@ import tech.libeufin.util.getNowUs import java.util.Random import java.util.UUID +// Foo pays Bar with custom subject. +fun genTx(subject: String = "test"): BankInternalTransaction = + BankInternalTransaction( + creditorAccountId = 2, + debtorAccountId = 1, + subject = subject, + amount = TalerAmount( 10, 0, "KUDOS"), + accountServicerReference = "acct-svcr-ref", + endToEndId = "end-to-end-id", + paymentInformationId = "pmtinfid", + transactionDate = 100000L + ) + class DatabaseTest { private val customerFoo = Customer( login = "foo", @@ -57,7 +70,7 @@ class DatabaseTest { hasDebt = false, maxDebt = TalerAmount(10, 1, "KUDOS") ) - + val fooPaysBar = genTx() @Test fun bearerTokenTest() { val db = initDb() @@ -95,16 +108,6 @@ class DatabaseTest { barId!!, TalerAmount(50, 0) ) - val fooPaysBar = BankInternalTransaction( - creditorAccountId = 2, - debtorAccountId = 1, - subject = "test", - amount = TalerAmount(10, 0), - accountServicerReference = "acct-svcr-ref", - endToEndId = "end-to-end-id", - paymentInformationId = "pmtinfid", - transactionDate = 100000L - ) val firstSpending = db.bankTransactionCreate(fooPaysBar) // Foo pays Bar and goes debit. assert(firstSpending == Database.BankTransactionResult.SUCCESS) fooAccount = db.bankAccountGetFromOwnerId(fooId) @@ -157,7 +160,7 @@ class DatabaseTest { assert(barAccount?.balance?.equals(TalerAmount(0, 0, "KUDOS")) == true) // Bringing Bar to debit. val barPaysMore = db.bankTransactionCreate(barPaysFoo) - assert(barPaysAgain == Database.BankTransactionResult.SUCCESS) + assert(barPaysMore == Database.BankTransactionResult.SUCCESS) barAccount = db.bankAccountGetFromOwnerId(barId) fooAccount = db.bankAccountGetFromOwnerId(fooId) // Bar: credit -> debit @@ -223,12 +226,24 @@ class DatabaseTest { @Test fun historyTest() { val db = initDb() - val res = db.bankTransactionGetHistory( - 10L, - 1L, - 1L + db.customerCreate(customerFoo); db.bankAccountCreate(bankAccountFoo) + db.customerCreate(customerBar); db.bankAccountCreate(bankAccountBar) + assert(db.bankAccountSetMaxDebt(1L, TalerAmount(10000000, 0))) + // Foo pays Bar 100 times: + for (i in 1..100) { db.bankTransactionCreate(genTx("test-$i")) } + // Testing positive delta: + val forward = db.bankTransactionGetHistory( + start = 50L, + delta = 2L, + bankAccountId = 1L // asking as Foo + ) + assert(forward.size == 2 && forward[0].dbRowId!! < forward[1].dbRowId!!) + val backward = db.bankTransactionGetHistory( + start = 50L, + delta = -2L, + bankAccountId = 1L // asking as Foo ) - assert(res.isEmpty()) + assert(backward.size == 2 && backward[0].dbRowId!! > backward[1].dbRowId!!) } @Test fun cashoutTest() { diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt b/bank/src/test/kotlin/LibeuFinApiTest.kt @@ -38,6 +38,39 @@ class LibeuFinApiTest { owningCustomerId = rowId ) + /** + * Testing GET /transactions. This test checks that the sign + * of delta gets honored by the HTTP handler, namely that the + * records appear in ASC or DESC order, according to the sign + * of delta. + */ + @Test + fun testHistory() { + val db = initDb() + val fooId = db.customerCreate(customerFoo); assert(fooId != null) + assert(db.bankAccountCreate(genBankAccount(fooId!!))) + val barId = db.customerCreate(customerBar); assert(barId != null) + assert(db.bankAccountCreate(genBankAccount(barId!!))) + for (i in 1..10) { db.bankTransactionCreate(genTx("test-$i")) } + testApplication { + application(webApp) + val asc = client.get("/accounts/foo/transactions?delta=2") { + basicAuth("foo", "pw") + expectSuccess = true + } + var obj = Json.decodeFromString<BankAccountTransactionsResponse>(asc.bodyAsText()) + assert(obj.transactions.size == 2) + assert(obj.transactions[0].row_id < obj.transactions[1].row_id) + val desc = client.get("/accounts/foo/transactions?delta=-2") { + basicAuth("foo", "pw") + expectSuccess = true + } + obj = Json.decodeFromString(desc.bodyAsText()) + assert(obj.transactions.size == 2) + assert(obj.transactions[0].row_id > obj.transactions[1].row_id) + } + } + // Testing the creation of bank transactions. @Test fun postTransactionsTest() {