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 }