libeufin

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

WireTransferApiTest.kt (7927B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2026 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 io.ktor.http.*
     22 import io.ktor.server.testing.*
     23 import org.junit.Test
     24 import tech.libeufin.common.*
     25 import tech.libeufin.common.crypto.CryptoUtil
     26 import tech.libeufin.nexus.*
     27 import tech.libeufin.nexus.cli.*
     28 import tech.libeufin.ebics.randEbicsId
     29 import java.time.Instant
     30 import kotlin.test.*
     31 
     32 class WireTransferApiTest {
     33     // GET /taler-wire-transfer-gateway/config
     34     @Test
     35     fun config() = serverSetup {
     36         client.get("/taler-wire-transfer-gateway/config").assertOkJson<WireTransferConfig>()
     37     }
     38 
     39     // POST /taler-wire-transfer-gateway/registration
     40     @Test
     41     fun registration() = serverSetup { db ->
     42         val (priv, pub) = EddsaPublicKey.randEdsaKeyPair()
     43         val amount = TalerAmount("KUDOS:55")
     44         val valid_req = obj {
     45             "credit_amount" to amount
     46             "type" to "reserve"
     47             "alg" to "ECDSA"
     48             "account_pub" to pub
     49             "authorization_pub" to pub
     50             "authorization_sig" to CryptoUtil.eddsaSign(pub.raw, priv)
     51             "recurrent" to false
     52         }
     53 
     54         val subjects = listOf(
     55             TransferSubject.QrBill(subjectFmtQrBill(pub), amount),
     56             TransferSubject.Simple("Taler MAP:$pub", amount),
     57         )
     58 
     59         // Valid
     60         client.post("/taler-wire-transfer-gateway/registration") {
     61             json(valid_req)
     62         }.assertOkJson<SubjectResult> {
     63             assertEquals(it.subjects, subjects)
     64         }
     65 
     66         // Idempotent
     67         client.post("/taler-wire-transfer-gateway/registration") {
     68             json(valid_req)
     69         }.assertOkJson<SubjectResult> {
     70             assertEquals(it.subjects, subjects)
     71         }
     72 
     73         // Bad signature
     74         client.post("/taler-wire-transfer-gateway/registration") {
     75             json(valid_req) {
     76                 "authorization_sig" to EddsaSignature.rand()
     77             }
     78         }.assertConflict(TalerErrorCode.BANK_BAD_SIGNATURE)
     79 
     80         // Check authorization field in incoming history
     81         val (testPriv, testAuth) = EddsaPublicKey.randEdsaKeyPair()
     82         val testKey = EddsaPublicKey.randEdsaKey()
     83         val testSig = CryptoUtil.eddsaSign(testKey.raw, testPriv)
     84         val qr = subjectFmtQrBill(testAuth)
     85         client.post("/taler-wire-transfer-gateway/registration") {
     86             json(valid_req) {
     87                 "account_pub" to testKey
     88                 "authorization_pub" to testAuth
     89                 "authorization_sig" to testSig
     90                 "recurrent" to true
     91             }
     92         }.assertOkJson<SubjectResult>()
     93         val cfg = NexusIngestConfig.default(AccountType.exchange)
     94         registerIncomingPayment(db, cfg, genInPay(qr))
     95         registerIncomingPayment(db, cfg, genInPay(qr))
     96         registerIncomingPayment(db, cfg, genInPay(qr))
     97         client.post("/taler-wire-transfer-gateway/registration") {
     98             json(valid_req) {
     99                 "type" to "kyc"
    100                 "account_pub" to testKey
    101                 "authorization_pub" to testAuth
    102                 "authorization_sig" to testSig
    103                 "recurrent" to true
    104             }
    105         }.assertOkJson<SubjectResult>()
    106         val otherPub = EddsaPublicKey.randEdsaKey()
    107         val otherSig = CryptoUtil.eddsaSign(otherPub.raw, testPriv)
    108         client.post("/taler-wire-transfer-gateway/registration") {
    109             json(valid_req) {
    110                 "type" to "reserve"
    111                 "account_pub" to otherPub
    112                 "authorization_pub" to testAuth
    113                 "authorization_sig" to otherSig
    114                 "recurrent" to true
    115             }
    116         }.assertOkJson<SubjectResult>()
    117         val lastPub = EddsaPublicKey.randEdsaKey()
    118         talerableIn(db, reserve_pub=lastPub)
    119         talerableKycIn(db, account_pub=lastPub)
    120         val history = client.getA("/taler-wire-gateway/history/incoming?limit=-5")
    121             .assertOkJson<IncomingHistory>().incoming_transactions.map {
    122                 when (it) {
    123                     is IncomingKycAuthTransaction -> Triple(it.account_pub, it.authorization_pub, it.authorization_sig)
    124                     is IncomingReserveTransaction -> Triple(it.reserve_pub, it.authorization_pub, it.authorization_sig)
    125                     else -> throw UnsupportedOperationException()
    126                 }
    127             }
    128         assertContentEquals(history, listOf(
    129             Triple(lastPub, null, null),
    130             Triple(lastPub, null, null),
    131             Triple(otherPub, testAuth, otherSig),
    132             Triple(testKey, testAuth, testSig),
    133             Triple(testKey, testAuth, testSig)
    134         ))
    135     }
    136 
    137     // DELETE /taler-wire-transfer-gateway/registration
    138     @Test
    139     fun unregistration() = serverSetup {
    140         val (priv, pub) = EddsaPublicKey.randEdsaKeyPair()
    141 
    142         // Unknown
    143         client.delete("/taler-wire-transfer-gateway/registration") {
    144             val now = Instant.now().toString()
    145             json {
    146                 "timestamp" to now
    147                 "authorization_pub" to pub
    148                 "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv)
    149             }
    150         }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
    151                 
    152         // Know
    153         client.post("/taler-wire-transfer-gateway/registration") {
    154             json {
    155                 "credit_amount" to "KUDOS:55"
    156                 "type" to "reserve"
    157                 "alg" to "ECDSA"
    158                 "account_pub" to pub
    159                 "authorization_pub" to pub
    160                 "authorization_sig" to CryptoUtil.eddsaSign(pub.raw, priv)
    161                 "recurrent" to false
    162             }
    163         }.assertOkJson<SubjectResult>()
    164         client.delete("/taler-wire-transfer-gateway/registration") {
    165             val now = Instant.now().toString()
    166             json {
    167                 "timestamp" to now
    168                 "authorization_pub" to pub
    169                 "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv)
    170             }
    171         }.assertNoContent()
    172 
    173         // Idempotent
    174         client.delete("/taler-wire-transfer-gateway/registration") {
    175             val now = Instant.now().toString()
    176             json {
    177                 "timestamp" to now
    178                 "authorization_pub" to pub
    179                 "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv)
    180             }
    181         }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
    182 
    183         // Bad signature
    184         client.delete("/taler-wire-transfer-gateway/registration") {
    185             val now = Instant.now().toString()
    186             json {
    187                 "timestamp" to now
    188                 "authorization_pub" to pub
    189                 "authorization_sig" to CryptoUtil.eddsaSign("lol".toByteArray(), priv)
    190             }
    191         }.assertConflict(TalerErrorCode.BANK_BAD_SIGNATURE)
    192 
    193         // Old timestamp
    194         client.delete("/taler-wire-transfer-gateway/registration") {
    195             val now = Instant.now().minusSeconds(1000000).toString()
    196             json {
    197                 "timestamp" to now
    198                 "authorization_pub" to pub
    199                 "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv)
    200             }
    201         }.assertConflict(TalerErrorCode.BANK_OLD_TIMESTAMP)
    202     }
    203 }