helpers.kt (8667B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024-2025 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.* 21 import io.ktor.client.request.* 22 import io.ktor.client.statement.* 23 import io.ktor.http.* 24 import io.ktor.server.testing.* 25 import kotlinx.coroutines.runBlocking 26 import tech.libeufin.common.* 27 import tech.libeufin.common.db.dbInit 28 import tech.libeufin.common.db.pgDataSource 29 import tech.libeufin.ebics.* 30 import tech.libeufin.nexus.* 31 import tech.libeufin.nexus.cli.registerIncomingPayment 32 import tech.libeufin.nexus.cli.registerOutgoingPayment 33 import tech.libeufin.nexus.db.Database 34 import tech.libeufin.nexus.db.InitiatedPayment 35 import tech.libeufin.nexus.iso20022.* 36 import java.time.Instant 37 import kotlin.io.path.Path 38 39 fun conf( 40 conf: String = "test.conf", 41 lambda: suspend (NexusConfig) -> Unit 42 ) = runBlocking { 43 val cfg = nexusConfig(Path("conf/$conf")) 44 lambda(cfg) 45 } 46 47 fun setup( 48 conf: String = "test.conf", 49 lambda: suspend (Database, NexusConfig) -> Unit 50 ) = conf(conf) { cfg -> 51 pgDataSource(cfg.dbCfg.dbConnStr).dbInit(cfg.dbCfg, "libeufin-nexus", true) 52 cfg.withDb(lambda) 53 } 54 55 fun serverSetup( 56 conf: String = "test.conf", 57 lambda: suspend ApplicationTestBuilder.(Database) -> Unit 58 ) = setup(conf) { db, cfg -> 59 testApplication { 60 application { 61 nexusApi(db, cfg) 62 } 63 lambda(db) 64 } 65 } 66 67 const val grothoffPayto = "payto://iban/CH4189144589712575493?receiver-name=Grothoff%20Hans" 68 69 val clientKeys = generateNewKeys() 70 71 /** Generates a payment initiation, given its subject */ 72 fun genInitPay( 73 endToEndId: String, 74 subject: String = "init payment", 75 amount: String = "KUDOS:44", 76 creditor: IbanPayto = ibanPayto("CH4189144589712575493", "Test") 77 ) = InitiatedPayment( 78 id = -1, 79 amount = TalerAmount(amount), 80 creditor = creditor, 81 subject = subject, 82 initiationTime = Instant.now(), 83 endToEndId = endToEndId 84 ) 85 86 /** Generates an incoming payment, given its subject */ 87 fun genInPay( 88 subject: String, 89 amount: String = "KUDOS:44", 90 executionTime: Instant = Instant.now() 91 ) = IncomingPayment( 92 amount = TalerAmount(amount), 93 debtor = ibanPayto("DE84500105177118117964"), 94 subject = subject, 95 executionTime = executionTime, 96 id = IncomingId(null, randEbicsId(), null) 97 ) 98 99 /** Generates an outgoing payment, given its subject and end-to-end ID */ 100 fun genOutPay( 101 subject: String, 102 endToEndId: String? = null, 103 msgId: String? = null, 104 executionTime: Instant = Instant.now() 105 ) = OutgoingPayment( 106 id = OutgoingId(msgId, endToEndId ?: randEbicsId(), null), 107 amount = TalerAmount(44, 0, "KUDOS"), 108 creditor = ibanPayto("CH4189144589712575493", "Test"), 109 subject = subject, 110 executionTime = executionTime, 111 ) 112 113 /** Perform a taler outgoing transaction */ 114 suspend fun ApplicationTestBuilder.transfer() { 115 client.postA("/taler-wire-gateway/transfer") { 116 json { 117 "request_uid" to HashCode.rand() 118 "amount" to "CHF:55" 119 "exchange_base_url" to "http://exchange.example.com/" 120 "wtid" to ShortHashCode.rand() 121 "credit_account" to grothoffPayto 122 } 123 }.assertOk() 124 } 125 126 /** Perform a taler incoming transaction of [amount] from merchant to exchange */ 127 suspend fun ApplicationTestBuilder.addIncoming(amount: String) { 128 client.postA("/taler-wire-gateway/admin/add-incoming") { 129 json { 130 "amount" to TalerAmount(amount) 131 "reserve_pub" to EddsaPublicKey.rand() 132 "debit_account" to grothoffPayto 133 } 134 }.assertOk() 135 } 136 137 /** Perform a taler kyc transaction of [amount] from merchant to exchange */ 138 suspend fun ApplicationTestBuilder.addKyc(amount: String) { 139 client.postA("/taler-wire-gateway/admin/add-kycauth") { 140 json { 141 "amount" to TalerAmount(amount) 142 "account_pub" to EddsaPublicKey.rand() 143 "debit_account" to grothoffPayto 144 } 145 }.assertOk() 146 } 147 148 /** Register a talerable outgoing transaction */ 149 suspend fun talerableOut(db: Database) { 150 val wtid = EddsaPublicKey.randEdsaKey() 151 registerOutgoingPayment(db, genOutPay("$wtid http://exchange.example.com/")) 152 } 153 154 /** Register a talerable reserve incoming transaction */ 155 suspend fun talerableIn(db: Database, amount: String = "CHF:44") { 156 val reserve_pub = EddsaPublicKey.randEdsaKey() 157 registerIncomingPayment(db, NexusIngestConfig.default(AccountType.exchange), 158 genInPay("test with $reserve_pub reserve pub", amount) 159 ) 160 } 161 162 /** Register an incomplete talerable reserve incoming transaction */ 163 suspend fun talerableIncompleteIn(db: Database) { 164 val reserve_pub = EddsaPublicKey.randEdsaKey() 165 val incomplete = genInPay("test with $reserve_pub reserve pub").copy(subject = null, debtor = null) 166 registerIncomingPayment(db, NexusIngestConfig.default(AccountType.exchange), incomplete) 167 } 168 169 /** Register a completed talerable reserve incoming transaction */ 170 suspend fun talerableCompletedIn(db: Database) { 171 val reserve_pub = EddsaPublicKey.randEdsaKey() 172 val original = genInPay("test with $reserve_pub reserve pub") 173 val incomplete = original.copy(subject = null, debtor = null) 174 registerIncomingPayment(db, NexusIngestConfig.default(AccountType.exchange), incomplete) 175 registerIncomingPayment(db, NexusIngestConfig.default(AccountType.exchange), original) 176 } 177 178 /** Register a talerable KYC incoming transaction */ 179 suspend fun talerableKycIn(db: Database, amount: String = "CHF:44") { 180 val account_pub = EddsaPublicKey.randEdsaKey() 181 registerIncomingPayment(db, NexusIngestConfig.default(AccountType.exchange), 182 genInPay("test with KYC:$account_pub account pub", amount) 183 ) 184 } 185 186 /** Register an incoming transaction */ 187 suspend fun registerIn(db: Database) { 188 registerIncomingPayment(db, NexusIngestConfig.default(AccountType.exchange), genInPay("ignored")) 189 } 190 191 /** Register an incomplete incoming transaction */ 192 suspend fun registerIncompleteIn(db: Database) { 193 val incomplete = genInPay("ignored").copy(subject = null, debtor = null) 194 registerIncomingPayment(db, NexusIngestConfig.default(AccountType.exchange), incomplete) 195 } 196 197 /** Register a completed incoming transaction */ 198 suspend fun registerCompletedIn(db: Database) { 199 val original = genInPay("ignored") 200 val incomplete = original.copy(subject = null, debtor = null) 201 registerIncomingPayment(db, NexusIngestConfig.default(AccountType.exchange), incomplete) 202 registerIncomingPayment(db, NexusIngestConfig.default(AccountType.exchange), original) 203 } 204 205 /** Register an outgoing transaction */ 206 suspend fun registerOut(db: Database) { 207 registerOutgoingPayment(db, genOutPay("ignored")) 208 } 209 210 /** Register an incomplete outgoing transaction */ 211 suspend fun registerIncompleteOut(db: Database) { 212 val original = genOutPay("ignored") 213 val incomplete = original.copy(id = OutgoingId(null, null, original.id.endToEndId), creditor = null) 214 registerOutgoingPayment(db, incomplete) 215 } 216 217 /* ----- Auth ----- */ 218 219 /** Auto auth get request */ 220 suspend inline fun HttpClient.getA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { 221 return get(url) { 222 auth() 223 builder(this) 224 } 225 } 226 227 /** Auto auth post request */ 228 suspend inline fun HttpClient.postA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { 229 return post(url) { 230 auth() 231 builder(this) 232 } 233 } 234 235 /** Auto auth patch request */ 236 suspend inline fun HttpClient.patchA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { 237 return patch(url) { 238 auth() 239 builder(this) 240 } 241 } 242 243 /** Auto auth delete request */ 244 suspend inline fun HttpClient.deleteA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { 245 return delete(url) { 246 auth() 247 builder(this) 248 } 249 } 250 251 fun HttpRequestBuilder.auth() { 252 headers[HttpHeaders.Authorization] = "Bearer secret-token" 253 }