libeufin

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

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 }