libeufin

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

commit 525a1b1e2ea9e17c6cf8c1fd927c66eb871b2e47
parent fbf7eb08bf6227e0573f141707a30fa695cba6b6
Author: Antoine A <>
Date:   Thu, 19 Oct 2023 08:27:11 +0000

fix error status and code

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 19++++++++-----------
Mbank/src/main/kotlin/tech/libeufin/bank/Metadata.kt | 2+-
Mbank/src/test/kotlin/CoreBankApiTest.kt | 36++++++++++++++++++------------------
Mbank/src/test/kotlin/WireGatewayApiTest.kt | 54+++++++++++++++++++++++++++++++++++++++---------------
Mbank/src/test/kotlin/helpers.kt | 1+
5 files changed, 67 insertions(+), 45 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -210,10 +210,10 @@ fun Routing.coreBankAccountsMgmtApi(db: Database, ctx: BankApplicationContext) { } // auth passed, proceed with activity. val req = call.receive<RegisterAccountRequest>() // Prohibit reserved usernames: - if (reservedAccounts.contains(req.username)) throw conflict( + if (reservedAccounts.contains(req.username)) throw forbidden( "Username '${req.username}' is reserved.", TalerErrorCode.TALER_EC_BANK_RESERVED_USERNAME_CONFLICT - ) // TODO conflict or forbidden ? + ) // Checking idempotency. val maybeCustomerExists = db.customerGetFromLogin(req.username) // Can be null if previous call crashed before completion. @@ -295,22 +295,19 @@ fun Routing.coreBankAccountsMgmtApi(db: Database, ctx: BankApplicationContext) { delete("/accounts/{USERNAME}") { val (login, _) = call.authCheck(db, TokenScope.readwrite, withAdmin = true, requireAdmin = ctx.restrictAccountDeletion) // Not deleting reserved names. - if (reservedAccounts.contains(login)) throw conflict( + if (reservedAccounts.contains(login)) throw forbidden( "Cannot delete reserved accounts", TalerErrorCode.TALER_EC_BANK_RESERVED_USERNAME_CONFLICT - ) // TODO conflict or forbidden ? + ) when (db.customerDeleteIfBalanceIsZero(login)) { CustomerDeletionResult.CUSTOMER_NOT_FOUND -> throw notFound( "Customer '$login' not found", - talerEc = TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT + TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT ) - CustomerDeletionResult.BALANCE_NOT_ZERO -> throw LibeufinBankException( - httpStatus = HttpStatusCode.PreconditionFailed, // PreconditionFailed or conflict ? - talerError = TalerError( - hint = "Balance is not zero.", - code = TalerErrorCode.TALER_EC_NONE.code // FIXME: need EC. - ) + CustomerDeletionResult.BALANCE_NOT_ZERO -> throw conflict( + "Balance is not zero.", + TalerErrorCode.TALER_EC_NONE // FIXME: need EC. ) CustomerDeletionResult.SUCCESS -> call.respond(HttpStatusCode.NoContent) } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt b/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt @@ -29,7 +29,7 @@ sealed interface TxMetadata { // OutgoingTxMetadata try { - val (wtid, exchangeBaseUrl) = subject.split(" ", limit=2) ; + val (wtid, exchangeBaseUrl) = subject.split(" ", limit=2) return OutgoingTxMetadata(ShortHashCode(wtid), ExchangeUrl(exchangeBaseUrl)) } catch (e: Exception) { } diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -9,6 +9,7 @@ import io.ktor.server.testing.* import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import net.taler.wallet.crypto.Base32Crockford +import net.taler.common.errorcodes.TalerErrorCode import org.junit.Test import org.postgresql.jdbc.PgConnection import tech.libeufin.bank.* @@ -59,7 +60,7 @@ class CoreBankAccountsMgmtApiTest { "password" to "password" "name" to "John Smith" }) - }.assertStatus(HttpStatusCode.Conflict) + }.assertForbidden().assertErr(TalerErrorCode.TALER_EC_BANK_RESERVED_USERNAME_CONFLICT) } } @@ -90,14 +91,13 @@ class CoreBankAccountsMgmtApiTest { // Unknown account client.delete("/accounts/unknown") { basicAuth("admin", "admin-password") - }.assertStatus(HttpStatusCode.NotFound) + }.assertNotFound().assertErr(TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT) // Reserved account reservedAccounts.forEach { client.delete("/accounts/$it") { basicAuth("admin", "admin-password") - expectSuccess = false - }.assertStatus(HttpStatusCode.Conflict) + }.assertForbidden().assertErr(TalerErrorCode.TALER_EC_BANK_RESERVED_USERNAME_CONFLICT) } // successful deletion @@ -114,7 +114,7 @@ class CoreBankAccountsMgmtApiTest { // Trying again must yield 404 client.delete("/accounts/john") { basicAuth("admin", "admin-password") - }.assertStatus(HttpStatusCode.NotFound) + }.assertNotFound().assertErr(TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT) // fail to delete, due to a non-zero balance. @@ -126,7 +126,7 @@ class CoreBankAccountsMgmtApiTest { }.assertOk() client.delete("/accounts/merchant") { basicAuth("admin", "admin-password") - }.assertStatus(HttpStatusCode.PreconditionFailed) + }.assertConflict() client.post("/accounts/merchant/transactions") { basicAuth("merchant", "merchant-password") jsonBody(json { @@ -167,7 +167,7 @@ class CoreBankAccountsMgmtApiTest { client.patch("/accounts/merchant") { basicAuth("merchant", "merchant-password") jsonBody(nameReq) - }.assertStatus(HttpStatusCode.Forbidden) + }.assertForbidden() // Finally checking that admin does get to patch foo's name. client.patch("/accounts/merchant") { basicAuth("admin", "admin-password") @@ -280,8 +280,8 @@ class CoreBankAccountsMgmtApiTest { // Check wrong user client.get("/accounts/exchange") { - basicAuth("merchanr", "merchanr-password") - }.assertStatus(HttpStatusCode.Unauthorized) + basicAuth("merchant", "merchant-password") + }.assertUnauthorized() } } @@ -294,19 +294,19 @@ class CoreBankTransactionsApiTest { client.request(path) { this.method = method basicAuth("unknown", "password") - }.assertStatus(HttpStatusCode.Unauthorized) + }.assertUnauthorized() // Wrong password client.request(path) { this.method = method basicAuth("merchant", "wrong-password") - }.assertStatus(HttpStatusCode.Unauthorized) + }.assertUnauthorized() // Wrong account client.request(path) { this.method = method basicAuth("exchange", "merchant-password") - }.assertStatus(HttpStatusCode.Unauthorized) + }.assertUnauthorized() // TODO check admin rights } @@ -451,11 +451,11 @@ class CoreBankTransactionsApiTest { // Check unknown transaction client.get("/accounts/merchant/transactions/3") { basicAuth("merchant", "merchant-password") - }.assertStatus(HttpStatusCode.NotFound) + }.assertNotFound().assertErr(TalerErrorCode.TALER_EC_BANK_TRANSACTION_NOT_FOUND) // Check wrong transaction client.get("/accounts/merchant/transactions/2") { basicAuth("merchant", "merchant-password") - }.assertStatus(HttpStatusCode.Unauthorized) // Should be NOT_FOUND ? + }.assertUnauthorized() // Should be NOT_FOUND ? } // POST /transactions @@ -515,7 +515,7 @@ class CoreBankTransactionsApiTest { jsonBody(json(valid_req) { "amount" to "EUR:3.3" }) - }.assertBadRequest() + }.assertBadRequest().assertErr(TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH) // Surpassing the debt limit client.post("/accounts/merchant/transactions") { basicAuth("merchant", "merchant-password") @@ -523,7 +523,7 @@ class CoreBankTransactionsApiTest { jsonBody(json(valid_req) { "amount" to "KUDOS:555" }) - }.assertStatus(HttpStatusCode.Conflict) + }.assertConflict().assertErr(TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT) // Missing message client.post("/accounts/merchant/transactions") { basicAuth("merchant", "merchant-password") @@ -539,7 +539,7 @@ class CoreBankTransactionsApiTest { jsonBody(json(valid_req) { "payto_uri" to "payto://iban/UNKNOWN-IBAN-XYZ?message=payout" }) - }.assertStatus(HttpStatusCode.NotFound) + }.assertNotFound().assertErr(TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT) // Transaction to self client.post("/accounts/merchant/transactions") { basicAuth("merchant", "merchant-password") @@ -547,7 +547,7 @@ class CoreBankTransactionsApiTest { jsonBody(json(valid_req) { "payto_uri" to "payto://iban/MERCHANT-IBAN-XYZ?message=payout" }) - }.assertStatus(HttpStatusCode.Conflict) + }.assertConflict().assertErr(TalerErrorCode.TALER_EC_BANK_SAME_ACCOUNT) } } diff --git a/bank/src/test/kotlin/WireGatewayApiTest.kt b/bank/src/test/kotlin/WireGatewayApiTest.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.* import org.junit.Test import tech.libeufin.bank.* import tech.libeufin.util.CryptoUtil +import net.taler.common.errorcodes.TalerErrorCode import java.util.* import java.time.Instant import kotlin.test.assertEquals @@ -54,26 +55,26 @@ class WireGatewayApiTest { client.request(path) { this.method = method basicAuth("unknown", "password") - }.assertStatus(HttpStatusCode.Unauthorized) + }.assertUnauthorized() // Wrong password client.request(path) { this.method = method basicAuth("merchant", "wrong-password") - }.assertStatus(HttpStatusCode.Unauthorized) + }.assertUnauthorized() // Wrong account client.request(path) { this.method = method basicAuth("exchange", "merchant-password") - }.assertStatus(HttpStatusCode.Unauthorized) + }.assertUnauthorized() // Not exchange account client.request(path) { this.method = method if (body != null) jsonBody(body) basicAuth("merchant", "merchant-password") - }.assertStatus(HttpStatusCode.Conflict) + }.assertConflict().assertErr(TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT) } // Testing the POST /transfer call from the TWG API. @@ -93,7 +94,7 @@ class WireGatewayApiTest { client.post("/accounts/exchange/taler-wire-gateway/transfer") { basicAuth("exchange", "exchange-password") jsonBody(valid_req) - }.assertStatus(HttpStatusCode.Conflict) + }.assertConflict().assertErr(TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT) // Giving debt allowance and checking the OK case. assert(db.bankAccountSetMaxDebt( @@ -120,7 +121,7 @@ class WireGatewayApiTest { "exchange_base_url" to "http://different-exchange.example.com/" } ) - }.assertStatus(HttpStatusCode.Conflict) + }.assertConflict().assertErr(TalerErrorCode.TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED) // Currency mismatch client.post("/accounts/exchange/taler-wire-gateway/transfer") { @@ -130,7 +131,7 @@ class WireGatewayApiTest { "amount" to "EUR:33" } ) - }.assertBadRequest() + }.assertBadRequest().assertErr(TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH) // Unknown account client.post("/accounts/exchange/taler-wire-gateway/transfer") { @@ -142,7 +143,19 @@ class WireGatewayApiTest { "credit_account" to "payto://iban/UNKNOWN-IBAN-XYZ" } ) - }.assertStatus(HttpStatusCode.NotFound) + }.assertNotFound().assertErr(TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT) + + // Same account + client.post("/accounts/exchange/taler-wire-gateway/transfer") { + basicAuth("exchange", "exchange-password") + jsonBody( + json(valid_req) { + "request_uid" to randHashCode() + "wtid" to randShortHashCode() + "credit_account" to "payto://iban/EXCHANGE-IBAN-XYZ" + } + ) + }.assertConflict().assertErr(TalerErrorCode.TALER_EC_BANK_SAME_ACCOUNT) // Bad BASE32 wtid client.post("/accounts/exchange/taler-wire-gateway/transfer") { @@ -229,7 +242,7 @@ class WireGatewayApiTest { // Check error when no transactions client.get("/accounts/exchange/taler-wire-gateway/history/incoming?delta=7") { basicAuth("exchange", "exchange-password") - }.assertStatus(HttpStatusCode.NoContent) + }.assertNoContent() // Gen three transactions using clean add incoming logic repeat(3) { @@ -408,7 +421,7 @@ class WireGatewayApiTest { // Check error when no transactions client.get("/accounts/exchange/taler-wire-gateway/history/outgoing?delta=7") { basicAuth("exchange", "exchange-password") - }.assertStatus(HttpStatusCode.NoContent) + }.assertNoContent() // Gen three transactions using clean transfer logic repeat(3) { @@ -499,7 +512,7 @@ class WireGatewayApiTest { client.post("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { basicAuth("exchange", "exchange-password") jsonBody(valid_req) - }.assertStatus(HttpStatusCode.Conflict) + }.assertConflict().assertErr(TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT) // Giving debt allowance and checking the OK case. assert(db.bankAccountSetMaxDebt( @@ -513,10 +526,10 @@ class WireGatewayApiTest { }.assertOk() // Trigger conflict due to reused reserve_pub - client.post("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { + client.post("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { basicAuth("exchange", "exchange-password") jsonBody(valid_req) - }.assertStatus(HttpStatusCode.Conflict) + }.assertConflict().assertErr(TalerErrorCode.TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT) // Currency mismatch client.post("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { @@ -526,7 +539,7 @@ class WireGatewayApiTest { "amount" to "EUR:33" } ) - }.assertBadRequest() + }.assertBadRequest().assertErr(TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH) // Unknown account client.post("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { @@ -537,7 +550,18 @@ class WireGatewayApiTest { "debit_account" to "payto://iban/UNKNOWN-IBAN-XYZ" } ) - }.assertStatus(HttpStatusCode.NotFound) + }.assertNotFound().assertErr(TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT) + + // Same account + client.post("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { + basicAuth("exchange", "exchange-password") + jsonBody( + json(valid_req) { + "reserve_pub" to randEddsaPublicKey() + "debit_account" to "payto://iban/EXCHANGE-IBAN-XYZ" + } + ) + }.assertConflict().assertErr(TalerErrorCode.TALER_EC_BANK_SAME_ACCOUNT) // Bad BASE32 reserve_pub client.post("/accounts/exchange/taler-wire-gateway/admin/add-incoming") { diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt @@ -110,6 +110,7 @@ fun HttpResponse.assertNotFound(): HttpResponse = assertStatus(HttpStatusCode.No fun HttpResponse.assertUnauthorized(): HttpResponse = assertStatus(HttpStatusCode.Unauthorized) fun HttpResponse.assertConflict(): HttpResponse = assertStatus(HttpStatusCode.Conflict) fun HttpResponse.assertBadRequest(): HttpResponse = assertStatus(HttpStatusCode.BadRequest) +fun HttpResponse.assertForbidden(): HttpResponse = assertStatus(HttpStatusCode.Forbidden) suspend fun HttpResponse.assertErr(code: TalerErrorCode): HttpResponse {