libeufin

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

commit 4887d87c0be652b21fcf6f2c6d6dde2b05dd4a66
parent 10bc544c731291090683bc67e421c1740f6dc269
Author: ms <ms@taler.net>
Date:   Wed, 22 Mar 2023 16:23:29 +0100

time ranged history (access API)

Diffstat:
Mnexus/src/test/kotlin/SandboxAccessApiTest.kt | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 29+++++++++++++++++------------
Mutil/src/main/kotlin/HTTP.kt | 16++++++++++++++++
3 files changed, 106 insertions(+), 12 deletions(-)

diff --git a/nexus/src/test/kotlin/SandboxAccessApiTest.kt b/nexus/src/test/kotlin/SandboxAccessApiTest.kt @@ -1,3 +1,4 @@ +import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import io.ktor.client.plugins.* import io.ktor.client.request.* @@ -63,6 +64,78 @@ class SandboxAccessApiTest { } } + // Tests the time range filter of Access API's GET /transactions + @Test + fun timeRangedTransactions() { + fun getTxs(respJson: String): JsonNode { + val mapper = ObjectMapper() + return mapper.readTree(respJson).get("transactions") + } + withTestDatabase { + prepSandboxDb() + testApplication { + application(sandboxApp) + var R = client.get("/demobanks/default/access-api/accounts/foo/transactions") { + expectSuccess = true + basicAuth("foo", "foo") + } + assert(getTxs(R.bodyAsText()).size() == 0) // Checking that no transactions exist. + wireTransfer( + "admin", + "foo", + "default", + "#0", + "TESTKUDOS:2" + ) + R = client.get("/demobanks/default/access-api/accounts/foo/transactions") { + expectSuccess = true + basicAuth("foo", "foo") + } + assert(getTxs(R.bodyAsText()).size() == 1) // Checking that #0 shows up. + // Asking up to a point in the past, where no txs should exist. + R = client.get("/demobanks/default/access-api/accounts/foo/transactions?until_ms=3000") { + expectSuccess = true + basicAuth("foo", "foo") + } + assert(getTxs(R.bodyAsText()).size() == 0) // Not expecting any transaction. + // Moving the transaction back in the past + transaction { + val tx_0 = BankAccountTransactionEntity.find { + BankAccountTransactionsTable.subject eq "#0" and + (BankAccountTransactionsTable.direction eq "CRDT") + }.first() + tx_0.date = 10000 + } + // Picking the past transaction from one including time range, + // therefore expecting one entry in the result + R = client.get("/demobanks/default/access-api/accounts/foo/transactions?from_ms=9000&until_ms=11000") { + expectSuccess = true + basicAuth("foo", "foo") + } + assert(getTxs(R.bodyAsText()).size() == 1) + // Not enough txs to fill the second page, expecting no txs therefore. + R = client.get("/demobanks/default/access-api/accounts/foo/transactions?page=2&size=1") { + expectSuccess = true + basicAuth("foo", "foo") + } + assert(getTxs(R.bodyAsText()).size() == 0) + // Creating one more tx and asking the second 1-sized page, expecting therefore one result. + wireTransfer( + "admin", + "foo", + "default", + "#1", + "TESTKUDOS:2" + ) + R = client.get("/demobanks/default/access-api/accounts/foo/transactions?page=2&size=1") { + expectSuccess = true + basicAuth("foo", "foo") + } + assert(getTxs(R.bodyAsText()).size() == 1) + } + } + } + // Tests for #7482 @Test fun highAmountWithdraw() { diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -1482,34 +1482,39 @@ val sandboxApp: Application.() -> Unit = { val authGranted: Boolean = bankAccount.isPublic || !WITH_AUTH || username == "admin" if (!authGranted && bankAccount.owner != username) throw forbidden("Cannot access bank account ${bankAccount.label}") - val page: Int = Integer.decode(call.request.queryParameters["page"] ?: "0") - val size: Int = Integer.decode(call.request.queryParameters["size"] ?: "5") - + // Paging values. + val page: Int = expectInt(call.request.queryParameters["page"] ?: "1") + val size: Int = expectInt(call.request.queryParameters["size"] ?: "5") + // Time range filter values + val fromMs = expectLong(call.request.queryParameters["from_ms"] ?: "0") + val untilMs = expectLong(call.request.queryParameters["until_ms"] ?: Long.MAX_VALUE.toString()) val ret = mutableListOf<RawPayment>() /** * Case where page number wasn't given, - * therefore the results starts from the last transaction. */ + * therefore the results starts from the last transaction. + */ transaction { /** * Get a history page - from the calling bank account - having * 'firstElementId' as the latest transaction in it. */ fun getPage(firstElementId: Long): Iterable<BankAccountTransactionEntity> { - logger.debug("History page from tx $firstElementId, including $size txs in the past.") + logger.debug("Trying to build pageBuf from ID: $firstElementId," + + " including $size txs in the past." + ) return BankAccountTransactionEntity.find { (BankAccountTransactionsTable.id lessEq firstElementId) and - (BankAccountTransactionsTable.account eq bankAccount.id) + (BankAccountTransactionsTable.account eq bankAccount.id) and + (BankAccountTransactionsTable.date.between(fromMs, untilMs)) }.sortedByDescending { it.id.value }.take(size) } val lt: BankAccountTransactionEntity? = bankAccount.lastTransaction if (lt == null) return@transaction var nextPageIdUpperLimit: Long = lt.id.value - /** - * This loop fetches (and discards) pages until the - * desired one is found. */ - for (i in 0..(page)) { + // This loop fetches (and discards) pages until the desired one is found. + for (i in 1..(page)) { val pageBuf = getPage(nextPageIdUpperLimit) - logger.debug("Processing page:") - pageBuf.forEach { logger.debug("${it.id} ${it.subject} ${it.amount}") } + logger.debug("pageBuf #$i follows. Request wants #$page:") + pageBuf.forEach { logger.debug("ID: ${it.id}, subject: ${it.subject}, amount: ${it.currency}:${it.amount}") } if (pageBuf.none()) return@transaction nextPageIdUpperLimit = pageBuf.last().id.value - 1 if (i == page) pageBuf.forEach { diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt @@ -186,3 +186,18 @@ fun extractUserAndPassword(authorizationHeader: String): Pair<String, String> { } return Pair(username, password) } + +fun expectInt(uriParam: String): Int { + return try { Integer.decode(uriParam) } + catch (e: Exception) { + logger.error(e.message) + throw badRequest("'$uriParam' is not Int") + } +} +fun expectLong(uriParam: String): Long { + return try { uriParam.toLong() } + catch (e: Exception) { + logger.error(e.message) + throw badRequest("'$uriParam' is not Long") + } +} +\ No newline at end of file