diff options
author | Antoine A <> | 2023-10-16 11:45:45 +0000 |
---|---|---|
committer | Antoine A <> | 2023-10-16 11:45:45 +0000 |
commit | dc895a89e77636bb46964ffbfd2d4d9194c6f286 (patch) | |
tree | 825f8fbd8011d8c3007ca98484f9d62161c22757 | |
parent | 3de53c1b7ed09bb61d375f33770dd5dc109576cc (diff) | |
download | libeufin-dc895a89e77636bb46964ffbfd2d4d9194c6f286.tar.gz libeufin-dc895a89e77636bb46964ffbfd2d4d9194c6f286.tar.bz2 libeufin-dc895a89e77636bb46964ffbfd2d4d9194c6f286.zip |
Improve and fix transaction creation endpoint
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 52 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 6 | ||||
-rw-r--r-- | bank/src/test/kotlin/CoreBankApiTest.kt (renamed from bank/src/test/kotlin/LibeuFinApiTest.kt) | 177 |
3 files changed, 109 insertions, 126 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt index 3526e8c3..220ad654 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -118,16 +118,9 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) { val publicAccounts = db.accountsGetPublic(ctx.currency) if (publicAccounts.isEmpty()) { call.respond(HttpStatusCode.NoContent) - return@get + } else { + call.respond(PublicAccountsResponse(publicAccounts)) } - call.respond( - PublicAccountsResponse().apply { - publicAccounts.forEach { - this.public_accounts.add(it) - } - } - ) - return@get } get("/accounts") { val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw unauthorized() @@ -138,19 +131,12 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) { val queryParam = if (maybeFilter != null) { "%${maybeFilter}%" } else "%" - val dbRes = db.accountsGetForAdmin(queryParam) - if (dbRes.isEmpty()) { + val accounts = db.accountsGetForAdmin(queryParam) + if (accounts.isEmpty()) { call.respond(HttpStatusCode.NoContent) - return@get + } else { + call.respond(ListBankAccountsResponse(accounts)) } - call.respond( - ListBankAccountsResponse().apply { - dbRes.forEach { element -> - this.accounts.add(element) - } - } - ) - return@get } post("/accounts") { // check if only admin is allowed to create new accounts if (ctx.restrictRegistration) { @@ -514,21 +500,17 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) { val resourceName = call.expectUriComponent("USERNAME") // admin has no rights here. if ((c.login != resourceName) && (call.getAuthToken() == null)) throw forbidden() val tx = call.receive<BankAccountTransactionCreate>() + val subject = tx.payto_uri.message ?: throw badRequest("Wire transfer lacks subject") - val debtorBankAccount = db.bankAccountGetFromOwnerId(c.expectRowId()) - ?: throw internalServerError("Debtor bank account not found") - if (tx.amount.currency != ctx.currency) throw badRequest( - "Wrong currency: ${tx.amount.currency}", + val amount = tx.payto_uri.amount ?: tx.amount + if (amount == null) throw badRequest("Wire transfer lacks amount") + if (amount.currency != ctx.currency) throw badRequest( + "Wrong currency: ${amount.currency}", talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH ) - if (!isBalanceEnough( - balance = debtorBankAccount.expectBalance(), - due = tx.amount, - hasBalanceDebt = debtorBankAccount.hasDebt, - maxDebt = debtorBankAccount.maxDebt - )) - throw conflict(hint = "Insufficient balance.", talerEc = TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT) - logger.info("creditor payto: ${tx.payto_uri}") + // TODO rewrite all thos database query in a single database function + val debtorBankAccount = db.bankAccountGetFromOwnerId(c.expectRowId()) + ?: throw internalServerError("Debtor bank account not found") val creditorBankAccount = db.bankAccountGetFromInternalPayto(tx.payto_uri) ?: throw notFound( "Creditor account not found", @@ -538,11 +520,10 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) { debtorAccountId = debtorBankAccount.expectRowId(), creditorAccountId = creditorBankAccount.expectRowId(), subject = subject, - amount = tx.amount, + amount = amount, transactionDate = Instant.now() ) - val res = db.bankTransactionCreate(dbInstructions) - when (res) { + when (db.bankTransactionCreate(dbInstructions)) { BankTransactionResult.BALANCE_INSUFFICIENT -> throw conflict( "Insufficient funds", TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT @@ -555,7 +536,6 @@ fun Routing.accountsMgmtApi(db: Database, ctx: BankApplicationContext) { BankTransactionResult.NO_DEBTOR -> throw internalServerError("Debtor not found despite the request was authenticated.") BankTransactionResult.SUCCESS -> call.respond(HttpStatusCode.OK) } - return@post } get("/accounts/{USERNAME}/transactions/{T_ID}") { val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw unauthorized() diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt index 8b410061..0222c78b 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -335,7 +335,7 @@ data class AccountMinimalData( */ @Serializable data class ListBankAccountsResponse( - val accounts: MutableList<AccountMinimalData> = mutableListOf() + val accounts: List<AccountMinimalData> ) /** @@ -357,7 +357,7 @@ data class AccountData( @Serializable data class BankAccountTransactionCreate( val payto_uri: IbanPayTo, - val amount: TalerAmount + val amount: TalerAmount? ) /* History element, either from GET /transactions/T_ID @@ -602,7 +602,7 @@ data class TransferResponse( */ @Serializable data class PublicAccountsResponse( - val public_accounts: MutableList<PublicAccount> = mutableListOf() + val public_accounts: List<PublicAccount> ) /** diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt index 17e60dcc..ea8cc055 100644 --- a/bank/src/test/kotlin/LibeuFinApiTest.kt +++ b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -49,15 +49,11 @@ class LibeuFinApiTest { ) @Test - fun getConfig() = setup { db, ctx -> - testApplication { - application { corebankWebApp(db, ctx) } - val r = client.get("/config") { - expectSuccess = true - } - println(r.bodyAsText()) - } - + fun getConfig() = bankSetup { _ -> + val r = client.get("/config") { + expectSuccess = true + }.assertOk() + println(r.bodyAsText()) } /** @@ -99,85 +95,92 @@ class LibeuFinApiTest { // Testing the creation of bank transactions. @Test - fun postTransactionsTest() = setup { db, ctx -> - // foo account - val fooId = db.customerCreate(customerFoo); - assert(fooId != null) - assert(db.bankAccountCreate(genBankAccount(fooId!!)) != null) - // bar account - val barId = db.customerCreate(customerBar); - assert(barId != null) - assert(db.bankAccountCreate(genBankAccount(barId!!)) != null) - // accounts exist, now create one transaction. - testApplication { - application { - corebankWebApp(db, ctx) - } - client.post("/accounts/foo/transactions") { - expectSuccess = true - basicAuth("foo", "pw") - contentType(ContentType.Application.Json) - // expectSuccess = true - setBody( - """{ - "payto_uri": "payto://iban/AC${barId}?message=payout", - "amount": "KUDOS:3.3" - } - """.trimIndent() - ) - } - // Getting the only tx that exists in the DB, hence has ID == 1. - val r = client.get("/accounts/foo/transactions/1") { - basicAuth("foo", "pw") - expectSuccess = true - } - val obj: BankAccountTransactionInfo = Json.decodeFromString(r.bodyAsText()) - assert(obj.subject == "payout") - // Testing the wrong currency. - val wrongCurrencyResp = client.post("/accounts/foo/transactions") { - expectSuccess = false - basicAuth("foo", "pw") - contentType(ContentType.Application.Json) - // expectSuccess = true - setBody( - """{ - "payto_uri": "payto://iban/AC${barId}?message=payout", - "amount": "EUR:3.3" - } - """.trimIndent() - ) - } - assert(wrongCurrencyResp.status == HttpStatusCode.BadRequest) - // Surpassing the debt limit. - val unallowedDebtResp = client.post("/accounts/foo/transactions") { - expectSuccess = false - basicAuth("foo", "pw") - contentType(ContentType.Application.Json) - // expectSuccess = true - setBody( - """{ - "payto_uri": "payto://iban/AC${barId}?message=payout", - "amount": "KUDOS:555" - } - """.trimIndent() - ) - } - assert(unallowedDebtResp.status == HttpStatusCode.Conflict) - val bigAmount = client.post("/accounts/foo/transactions") { - expectSuccess = false - basicAuth("foo", "pw") - contentType(ContentType.Application.Json) - // expectSuccess = true - setBody( - """{ - "payto_uri": "payto://iban/AC${barId}?message=payout", - "amount": "KUDOS:${"5".repeat(200)}" - } - """.trimIndent() - ) - } - assert(bigAmount.status == HttpStatusCode.BadRequest) + fun postTransactionsTest() = bankSetup { _ -> + val valid_req = json { + "payto_uri" to "payto://iban/EXCHANGE-IBAN-XYZ?message=payout" + "amount" to "KUDOS:0.3" + } + + // Check ok + client.post("/accounts/merchant/transactions") { + basicAuth("merchant", "merchant-password") + jsonBody(valid_req) + }.assertOk() + client.get("/accounts/merchant/transactions/1") { + basicAuth("merchant", "merchant-password") + }.assertOk().run { + val tx: BankAccountTransactionInfo = Json.decodeFromString(bodyAsText()) + assertEquals("payout", tx.subject) + assertEquals(TalerAmount("KUDOS:0.3"), tx.amount) + } + // Check amount in payto_uri + client.post("/accounts/merchant/transactions") { + basicAuth("merchant", "merchant-password") + jsonBody(json { + "payto_uri" to "payto://iban/EXCHANGE-IBAN-XYZ?message=payout2&amount=KUDOS:1.05" + }) + }.assertOk() + client.get("/accounts/merchant/transactions/3") { + basicAuth("merchant", "merchant-password") + }.assertOk().run { + val tx: BankAccountTransactionInfo = Json.decodeFromString(bodyAsText()) + assertEquals("payout2", tx.subject) + assertEquals(TalerAmount("KUDOS:1.05"), tx.amount) + } + // Check amount in payto_uri precedence + client.post("/accounts/merchant/transactions") { + basicAuth("merchant", "merchant-password") + jsonBody(json { + "payto_uri" to "payto://iban/EXCHANGE-IBAN-XYZ?message=payout3&amount=KUDOS:1.05" + "amount" to "KUDOS:10.003" + }) + }.assertOk() + client.get("/accounts/merchant/transactions/5") { + basicAuth("merchant", "merchant-password") + }.assertOk().run { + val tx: BankAccountTransactionInfo = Json.decodeFromString(bodyAsText()) + assertEquals("payout3", tx.subject) + assertEquals(TalerAmount("KUDOS:1.05"), tx.amount) } + // Testing the wrong currency + client.post("/accounts/merchant/transactions") { + basicAuth("merchant", "merchant-password") + jsonBody(json(valid_req) { + "amount" to "EUR:3.3" + }) + }.assertBadRequest() + // Surpassing the debt limit + client.post("/accounts/merchant/transactions") { + basicAuth("merchant", "merchant-password") + contentType(ContentType.Application.Json) + jsonBody(json(valid_req) { + "amount" to "KUDOS:555" + }) + }.assertStatus(HttpStatusCode.Conflict) + // Missing message + client.post("/accounts/merchant/transactions") { + basicAuth("merchant", "merchant-password") + contentType(ContentType.Application.Json) + jsonBody(json(valid_req) { + "payto_uri" to "payto://iban/EXCHANGE-IBAN-XYZ" + }) + }.assertBadRequest() + // Unknown account + client.post("/accounts/merchant/transactions") { + basicAuth("merchant", "merchant-password") + contentType(ContentType.Application.Json) + jsonBody(json(valid_req) { + "payto_uri" to "payto://iban/UNKNOWN-IBAN-XYZ?message=payout" + }) + }.assertStatus(HttpStatusCode.NotFound) + // Transaction to self + client.post("/accounts/merchant/transactions") { + basicAuth("merchant", "merchant-password") + contentType(ContentType.Application.Json) + jsonBody(json(valid_req) { + "payto_uri" to "payto://iban/MERCHANT-IBAN-XYZ?message=payout" + }) + }.assertStatus(HttpStatusCode.Conflict) } @Test |