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