PreparedTransferApiTest.kt (7566B)
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 = SubjectRequest( 42 Payto.parse("payto://iban/CH7789144474425692816"), 43 TransferType.reserve, 44 false, 45 amount, 46 PublicKeyAlg.EdDSA, 47 pub, 48 pub, 49 EddsaSignature.rand() 50 ) 51 52 val simple = listOf(TransferSubject.Simple("Taler MAP:$pub", amount)) 53 val qrs = listOf(TransferSubject.QrBill(subjectFmtQrBill(pub), amount)) 54 55 // Valid simple 56 client.post("/taler-prepared-transfer/registration") { 57 json(valid_req.sign(priv)) 58 }.assertOkJson<SubjectResult> { 59 assertEquals(it.subjects, simple) 60 } 61 62 // Idempotent simple 63 client.post("/taler-prepared-transfer/registration") { 64 json(valid_req.sign(priv)) 65 }.assertOkJson<SubjectResult> { 66 assertEquals(it.subjects, simple) 67 } 68 69 // Valid qr 70 client.post("/taler-prepared-transfer/registration") { 71 json(valid_req.copy(credit_account=Payto.parse("payto://iban/CH4431999123000889012")).sign(priv)) 72 }.assertOkJson<SubjectResult> { 73 assertEquals(it.subjects, qrs) 74 } 75 76 // Idempotent qr 77 client.post("/taler-prepared-transfer/registration") { 78 json(valid_req.copy(credit_account=Payto.parse("payto://iban/CH4431999123000889012")).sign(priv)) 79 }.assertOkJson<SubjectResult> { 80 assertEquals(it.subjects, qrs) 81 } 82 83 // Bad signature 84 client.post("/taler-prepared-transfer/registration") { 85 json(valid_req) 86 }.assertForbidden(TalerErrorCode.BANK_BAD_SIGNATURE) 87 88 // Unknown account 89 client.post("/taler-prepared-transfer/registration") { 90 json(valid_req.copy(credit_account=Payto.parse(grothoffPayto)).sign(priv)) 91 }.assertConflict(TalerErrorCode.BANK_UNKNOWN_CREDITOR) 92 93 // Check authorization field in incoming history 94 val (testPriv, testAuth) = EddsaPublicKey.randEdsaKeyPair() 95 val testKey = EddsaPublicKey.randEdsaKey() 96 val qr = subjectFmtQrBill(testAuth) 97 client.post("/taler-prepared-transfer/registration") { 98 json(valid_req.copy( 99 credit_account=Payto.parse("payto://iban/CH4431999123000889012"), 100 account_pub=testKey, 101 authorization_pub=testAuth, 102 recurrent=true 103 ).sign(testPriv)) 104 }.assertOkJson<SubjectResult>() 105 val cfg = NexusIngestConfig.default(AccountType.exchange) 106 registerIncomingPayment(db, cfg, genInPay(qr)) 107 registerIncomingPayment(db, cfg, genInPay(qr)) 108 registerIncomingPayment(db, cfg, genInPay(qr)) 109 client.post("/taler-prepared-transfer/registration") { 110 json(valid_req.copy( 111 type=TransferType.kyc, 112 account_pub=testKey, 113 authorization_pub=testAuth, 114 recurrent=true 115 ).sign(testPriv)) 116 }.assertOkJson<SubjectResult>() 117 val otherPub = EddsaPublicKey.randEdsaKey() 118 client.post("/taler-prepared-transfer/registration") { 119 json(valid_req.copy( 120 account_pub=otherPub, 121 authorization_pub=testAuth, 122 recurrent=true 123 ).sign(testPriv)) 124 }.assertOkJson<SubjectResult>() 125 val lastPub = EddsaPublicKey.randEdsaKey() 126 talerableIn(db, reserve_pub=lastPub) 127 talerableKycIn(db, account_pub=lastPub) 128 val history = client.getA("/taler-wire-gateway/history/incoming?limit=-5") 129 .assertOkJson<IncomingHistory>().incoming_transactions.map { 130 when (it) { 131 is IncomingKycAuthTransaction -> Pair(it.account_pub, it.authorization_pub) 132 is IncomingReserveTransaction -> Pair(it.reserve_pub, it.authorization_pub) 133 else -> throw UnsupportedOperationException() 134 } 135 } 136 assertContentEquals(history, listOf( 137 Pair(lastPub, null), 138 Pair(lastPub, null), 139 Pair(otherPub, testAuth), 140 Pair(testKey, testAuth), 141 Pair(testKey, testAuth) 142 )) 143 } 144 145 // DELETE /taler-prepared-transfer/registration 146 @Test 147 fun unregistration() = serverSetup { 148 val (priv, pub) = EddsaPublicKey.randEdsaKeyPair() 149 val now = TalerTimestamp(Instant.now()) 150 val req = Unregistration( 151 now, 152 pub, 153 EddsaSignature.rand() 154 ).sign(priv) 155 156 // Unknown 157 client.post("/taler-prepared-transfer/unregistration") { 158 json(req) 159 }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND) 160 161 // Know 162 client.post("/taler-prepared-transfer/registration") { 163 json(SubjectRequest( 164 Payto.parse("payto://iban/CH7789144474425692816"), 165 TransferType.reserve, 166 false, 167 TalerAmount("KUDOS:55"), 168 PublicKeyAlg.EdDSA, 169 pub, 170 pub, 171 EddsaSignature.rand() 172 ).sign(priv)) 173 }.assertOkJson<SubjectResult>() 174 client.post("/taler-prepared-transfer/unregistration") { 175 json(req) 176 }.assertNoContent() 177 178 // Idempotent 179 client.post("/taler-prepared-transfer/unregistration") { 180 json(req) 181 }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND) 182 183 // Bad signature 184 client.post("/taler-prepared-transfer/unregistration") { 185 json(Unregistration( 186 now, 187 pub, 188 EddsaSignature.rand() 189 )) 190 }.assertForbidden(TalerErrorCode.BANK_BAD_SIGNATURE) 191 192 // Old timestamp 193 client.post("/taler-prepared-transfer/unregistration") { 194 json( 195 Unregistration( 196 TalerTimestamp(Instant.now().minusSeconds(1000000)), 197 pub, 198 EddsaSignature.rand() 199 ).sign(priv) 200 ) 201 }.assertConflict(TalerErrorCode.BANK_OLD_TIMESTAMP) 202 } 203 }