libeufin

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

BankIntegrationApiTest.kt (12056B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2023 Taler Systems S.A.
      4 
      5  * LibEuFin is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU Affero General Public License as
      7  * published by the Free Software Foundation; either version 3, or
      8  * (at your option) any later version.
      9 
     10  * LibEuFin is distributed in the hope that it will be useful, but
     11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13  * Public License for more details.
     14 
     15  * You should have received a copy of the GNU Affero General Public
     16  * License along with LibEuFin; see the file COPYING.  If not, see
     17  * <http://www.gnu.org/licenses/>
     18  */
     19 
     20 import io.ktor.client.request.*
     21 import org.junit.Test
     22 import tech.libeufin.bank.BankAccountCreateWithdrawalResponse
     23 import tech.libeufin.bank.BankWithdrawalOperationPostResponse
     24 import tech.libeufin.bank.BankWithdrawalOperationStatus
     25 import tech.libeufin.bank.WithdrawalStatus
     26 import tech.libeufin.common.*
     27 import tech.libeufin.common.test.*
     28 import java.util.*
     29 import kotlin.test.assertEquals
     30 
     31 class BankIntegrationApiTest {
     32     // GET /taler-integration/config
     33     @Test
     34     fun config() = bankSetup {
     35         client.get("/taler-integration/config").assertOk()
     36     }
     37 
     38     // GET /taler-integration/withdrawal-operation/UUID
     39     @Test
     40     fun get() = bankSetup {
     41         // Check OK
     42         for (valid in listOf(
     43             Pair(null, null),
     44             Pair("KUDOS:1.0", null),
     45             Pair(null, "KUDOS:2.0") ,
     46             Pair("KUDOS:3.0", "KUDOS:4.0")
     47         )) {
     48             val amount = valid.first?.run(::TalerAmount)
     49             val suggested = valid.second?.run(::TalerAmount)
     50             client.postA("/accounts/merchant/withdrawals") {
     51                 json { 
     52                     "amount" to amount
     53                     "suggested_amount" to suggested
     54                 }
     55             }.assertOkJson<BankAccountCreateWithdrawalResponse> {
     56                 val uuid = it.taler_withdraw_uri.split("/").last()
     57                 client.get("/taler-integration/withdrawal-operation/$uuid")
     58                     .assertOkJson<BankWithdrawalOperationStatus> {
     59                     assert(!it.selection_done)
     60                     assert(!it.aborted)
     61                     assert(!it.transfer_done)
     62                     assertEquals(it.card_fees, TalerAmount.zero("KUDOS"))
     63                     assertEquals(it.min_amount, TalerAmount.zero("KUDOS"))
     64                     assertEquals(it.max_amount, TalerAmount("KUDOS:10"))
     65                     assertEquals(amount, it.amount)
     66                     assertEquals(suggested, it.suggested_amount)
     67                     assertEquals(listOf("iban"), it.wire_types)
     68                     assertEquals("KUDOS", it.currency)
     69                 }
     70             }
     71         }
     72 
     73         // Check polling
     74         statusRoutine<BankWithdrawalOperationStatus>("/taler-integration/withdrawal-operation") { it.status }
     75 
     76         // Check unknown
     77         client.get("/taler-integration/withdrawal-operation/${UUID.randomUUID()}")
     78             .assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
     79         
     80         // Check bad UUID
     81         client.get("/taler-integration/withdrawal-operation/chocolate")
     82             .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
     83     }
     84 
     85     // POST /taler-integration/withdrawal-operation/UUID
     86     @Test
     87     fun select() = bankSetup {
     88         val reserve_pub = EddsaPublicKey.rand()
     89         val req = obj {
     90             "reserve_pub" to reserve_pub
     91             "selected_exchange" to exchangePayto.canonical
     92         }
     93 
     94         // Check bad UUID
     95         client.post("/taler-integration/withdrawal-operation/chocolate") {
     96             json(req)
     97         }.assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
     98 
     99         // Check unknown
    100         client.post("/taler-integration/withdrawal-operation/${UUID.randomUUID()}") {
    101             json(req)
    102         }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
    103 
    104         client.postA("/accounts/merchant/withdrawals") {
    105             json { "amount" to "KUDOS:1" } 
    106         }.assertOkJson<BankAccountCreateWithdrawalResponse> {
    107             val uuid = it.withdrawal_id
    108 
    109             // Check OK
    110             client.post("/taler-integration/withdrawal-operation/$uuid") {
    111                 json(req)
    112             }.assertOkJson<BankWithdrawalOperationPostResponse> {
    113                 assertEquals(WithdrawalStatus.selected, it.status)
    114                 assertEquals("http://localhost:8080/webui/#/operation/$uuid", it.confirm_transfer_url)
    115             }
    116             // Check idempotence
    117             client.post("/taler-integration/withdrawal-operation/$uuid") {
    118                 json(req)
    119             }.assertOkJson<BankWithdrawalOperationPostResponse> {
    120                 assertEquals(WithdrawalStatus.selected, it.status)
    121                 assertEquals("http://localhost:8080/webui/#/operation/$uuid", it.confirm_transfer_url)
    122             }
    123             // Check already selected
    124             client.post("/taler-integration/withdrawal-operation/$uuid") {
    125                 json(req) {
    126                     "reserve_pub" to EddsaPublicKey.rand()
    127                 }
    128             }.assertConflict(TalerErrorCode.BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT)
    129         }   
    130 
    131         client.postA("/accounts/merchant/withdrawals") {
    132             json { "amount" to "KUDOS:1" } 
    133         }.assertOkJson<BankAccountCreateWithdrawalResponse> {
    134             val uuid = it.withdrawal_id
    135 
    136             // Check reserve_pub_reuse
    137             client.post("/taler-integration/withdrawal-operation/$uuid") {
    138                 json(req)
    139             }.assertConflict(TalerErrorCode.BANK_DUPLICATE_RESERVE_PUB_SUBJECT)
    140 
    141             // Check amount differs
    142             client.post("/taler-integration/withdrawal-operation/$uuid") {
    143                 json(req) {
    144                     "amount" to "KUDOS:2"
    145                 }
    146             }.assertConflict(TalerErrorCode.BANK_AMOUNT_DIFFERS)
    147 
    148             // Check unknown account
    149             client.post("/taler-integration/withdrawal-operation/$uuid") {
    150                 json {
    151                     "reserve_pub" to EddsaPublicKey.rand()
    152                     "selected_exchange" to unknownPayto
    153                 }
    154             }.assertConflict(TalerErrorCode.BANK_UNKNOWN_ACCOUNT)
    155 
    156             // Check account not exchange
    157             client.post("/taler-integration/withdrawal-operation/$uuid") {
    158                 json {
    159                     "reserve_pub" to EddsaPublicKey.rand()
    160                     "selected_exchange" to merchantPayto
    161                 }
    162             }.assertConflict(TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE)
    163 
    164             client.post("/taler-integration/withdrawal-operation/$uuid") {
    165                 json {
    166                     "reserve_pub" to EddsaPublicKey.rand()
    167                     "selected_exchange" to exchangePayto.canonical
    168                     "amount" to "KUDOS:1"
    169                 }
    170             }.assertOkJson<BankWithdrawalOperationPostResponse>()
    171         }
    172 
    173         // Check select aborted
    174         client.postA("/accounts/merchant/withdrawals") {
    175             json { "amount" to "KUDOS:1" } 
    176         }.assertOkJson<BankAccountCreateWithdrawalResponse> {
    177             val uuid = it.withdrawal_id
    178             client.postA("/accounts/merchant/withdrawals/$uuid/abort").assertNoContent()
    179             
    180             // Check error
    181             client.postA("/taler-integration/withdrawal-operation/$uuid") {
    182                 json {
    183                     "reserve_pub" to EddsaPublicKey.rand()
    184                     "selected_exchange" to exchangePayto.canonical
    185                 }
    186             }.assertConflict(TalerErrorCode.BANK_UPDATE_ABORT_CONFLICT)
    187         }
    188 
    189         client.postA("/accounts/merchant/withdrawals") {
    190             json {}
    191         }.assertOkJson<BankAccountCreateWithdrawalResponse> {
    192             val uuid = it.withdrawal_id
    193 
    194             // Check insufficient fund
    195             client.post("/taler-integration/withdrawal-operation/$uuid") {
    196                 json {
    197                     "reserve_pub" to EddsaPublicKey.rand()
    198                     "selected_exchange" to exchangePayto.canonical
    199                     "amount" to "KUDOS:11"
    200                 }
    201             }.assertConflict(TalerErrorCode.BANK_UNALLOWED_DEBIT)
    202 
    203             client.post("/taler-integration/withdrawal-operation/$uuid") {
    204                 json {
    205                     "reserve_pub" to EddsaPublicKey.rand()
    206                     "selected_exchange" to exchangePayto.canonical
    207                     "amount" to "KUDOS:1.1"
    208                 }
    209             }.assertOkJson<BankWithdrawalOperationPostResponse>()
    210             client.get("/taler-integration/withdrawal-operation/$uuid")
    211                 .assertOkJson<BankWithdrawalOperationStatus> {
    212                 assertEquals(TalerAmount("KUDOS:1.1"), it.amount)
    213                 assertEquals(TalerAmount("KUDOS:10"), it.max_amount)
    214             }
    215         }
    216     }
    217 
    218     @Test
    219     fun selectWithFee() = bankSetup(conf = "test_with_fees.conf") {
    220         val uuid = client.postA("/accounts/merchant/withdrawals") {
    221             json {}
    222         }.assertOkJson<BankAccountCreateWithdrawalResponse>().withdrawal_id
    223         // Check insufficient fund
    224         for (amount in listOf("KUDOS:11", "KUDOS:10", "KUDOS:0", "KUDOS:150")) {
    225             client.post("/taler-integration/withdrawal-operation/$uuid") {
    226                 json {
    227                     "reserve_pub" to EddsaPublicKey.rand()
    228                     "selected_exchange" to exchangePayto.canonical
    229                     "amount" to amount
    230                 }
    231             }.assertConflict(TalerErrorCode.BANK_UNALLOWED_DEBIT)
    232         }
    233 
    234         // Check OK
    235         client.post("/taler-integration/withdrawal-operation/$uuid") {
    236             json {
    237                 "reserve_pub" to EddsaPublicKey.rand()
    238                 "selected_exchange" to exchangePayto.canonical
    239                 "amount" to "KUDOS:9"
    240             }
    241         }.assertOk()
    242     }
    243 
    244     // POST /taler-integration/withdrawal-operation/UUID/abort
    245     @Test
    246     fun abort() = bankSetup {
    247         // Check abort created
    248         client.postA("/accounts/merchant/withdrawals") {
    249             json { "amount" to "KUDOS:1" } 
    250         }.assertOkJson<BankAccountCreateWithdrawalResponse> {
    251             val uuid = it.withdrawal_id
    252 
    253             // Check OK
    254             client.postA("/taler-integration/withdrawal-operation/$uuid/abort").assertNoContent()
    255             // Check idempotence
    256             client.postA("/taler-integration/withdrawal-operation/$uuid/abort").assertNoContent()
    257         }
    258 
    259         // Check abort selected
    260         client.postA("/accounts/merchant/withdrawals") {
    261             json { "amount" to "KUDOS:1" } 
    262         }.assertOkJson<BankAccountCreateWithdrawalResponse> {
    263             val uuid = it.withdrawal_id
    264             withdrawalSelect(uuid)
    265 
    266             // Check OK
    267             client.postA("/taler-integration/withdrawal-operation/$uuid/abort").assertNoContent()
    268             // Check idempotence
    269             client.postA("/taler-integration/withdrawal-operation/$uuid/abort").assertNoContent()
    270         }
    271 
    272         // Check abort confirmed
    273         client.postA("/accounts/merchant/withdrawals") {
    274             json { "amount" to "KUDOS:1" } 
    275         }.assertOkJson<BankAccountCreateWithdrawalResponse> {
    276             val uuid = it.withdrawal_id
    277             withdrawalSelect(uuid)
    278             client.postA("/accounts/merchant/withdrawals/$uuid/confirm").assertNoContent()
    279 
    280             // Check error
    281             client.postA("/taler-integration/withdrawal-operation/$uuid/abort")
    282                 .assertConflict(TalerErrorCode.BANK_ABORT_CONFIRM_CONFLICT)
    283         }
    284 
    285         // Check bad UUID
    286         client.postA("/taler-integration/withdrawal-operation/chocolate/abort")
    287             .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
    288 
    289         // Check unknown
    290         client.postA("/taler-integration/withdrawal-operation/${UUID.randomUUID()}/abort")
    291             .assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
    292     }
    293 }