ConversionApiTest.kt (10301B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2023-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.request.* 21 import io.ktor.http.* 22 import org.junit.Test 23 import tech.libeufin.bank.* 24 import tech.libeufin.common.* 25 import tech.libeufin.common.test.* 26 import kotlin.test.assertEquals 27 28 class ConversionApiTest { 29 // GET /conversion-info/config 30 @Test 31 fun config() = bankSetup { 32 client.get("/conversion-info/config").assertOkJson<ConversionConfig>() 33 client.get("/conversion-rate-classes/1/conversion-info/config").assertOkJson<ConversionConfig>() 34 client.get("/accounts/merchant/conversion-info/config").assertOkJson<ConversionConfig>() 35 } 36 37 // POST /conversion-info/conversion-rate 38 @Test 39 fun conversionRate() = bankSetup { 40 val ok = obj { 41 "cashin_ratio" to "0.8" 42 "cashin_fee" to "KUDOS:0.02" 43 "cashin_tiny_amount" to "KUDOS:0.01" 44 "cashin_rounding_mode" to "nearest" 45 "cashin_min_amount" to "EUR:0" 46 "cashout_ratio" to "1.25" 47 "cashout_fee" to "EUR:0.003" 48 "cashout_tiny_amount" to "EUR:0.01" 49 "cashout_rounding_mode" to "zero" 50 "cashout_min_amount" to "KUDOS:0.1" 51 } 52 authRoutine(HttpMethod.Post, "/conversion-info/conversion-rate", requireAdmin = true) 53 authRoutine(HttpMethod.Post, "/accounts/merchant/conversion-info/conversion-rate", requireAdmin = true) 54 authRoutine(HttpMethod.Post, "/conversion-rate-classes/1/conversion-info/conversion-rate", requireAdmin = true) 55 for (prefix in sequenceOf("", "/conversion-rate-classes/1", "/accounts/merchant")) { 56 // Good rates 57 client.postAdmin("$prefix/conversion-info/conversion-rate") { 58 json(ok) 59 }.assertNoContent() 60 // Bad currency 61 client.postAdmin("$prefix/conversion-info/conversion-rate") { 62 json(ok) { 63 "cashout_fee" to "CHF:0.003" 64 } 65 }.assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH) 66 // Zero tiny amount 67 client.postAdmin("$prefix/conversion-info/conversion-rate") { 68 json(ok) { 69 "cashout_tiny_amount" to "EUR:0" 70 } 71 }.assertBadRequest(TalerErrorCode.GENERIC_JSON_INVALID) 72 client.postAdmin("$prefix/conversion-info/conversion-rate") { 73 json(ok) { 74 "cashin_tiny_amount" to "KUDOS:0" 75 } 76 }.assertBadRequest(TalerErrorCode.GENERIC_JSON_INVALID) 77 // Subcent cashout tiny amount 78 client.postAdmin("$prefix/conversion-info/conversion-rate") { 79 json(ok) { 80 "cashout_tiny_amount" to "EUR:0.0001" 81 } 82 }.assertBadRequest(TalerErrorCode.GENERIC_JSON_INVALID) 83 } 84 } 85 86 // GET /conversion-info/rate 87 @Test 88 fun userRate() = bankSetup { 89 authRoutine(HttpMethod.Get, "/accounts/merchant/conversion-info/rate", allowAdmin = true, optional = true) 90 authRoutine(HttpMethod.Get, "/conversion-rate-classes/1/conversion-info/rate", requireAdmin = true) 91 client.get("/conversion-info/rate").assertOkJson<ConversionRate>() 92 client.getA("/accounts/merchant/conversion-info/rate").assertOkJson<ConversionRate>() 93 client.get("/accounts/exchange/conversion-info/rate").assertOkJson<ConversionRate>() 94 client.getAdmin("/conversion-rate-classes/1/conversion-info/rate").assertOkJson<ConversionRate>() 95 } 96 97 // GET /conversion-info/cashout-rate 98 @Test 99 fun cashoutRate() = bankSetup { 100 authRoutine(HttpMethod.Get, "/accounts/merchant/conversion-info/cashout-rate?amount_debit=KUDOS:1", allowAdmin = true) 101 authRoutine(HttpMethod.Get, "/conversion-rate-classes/1/conversion-info/cashout-rate?amount_debit=KUDOS:1", requireAdmin = true) 102 for (prefix in sequenceOf("", "/conversion-rate-classes/1", "/accounts/merchant")) { 103 // Check conversion to 104 client.getAdmin("$prefix/conversion-info/cashout-rate?amount_debit=KUDOS:1").assertOkJson<ConversionResponse> { 105 assertEquals(TalerAmount("KUDOS:1"), it.amount_debit) 106 assertEquals(TalerAmount("EUR:1.25"), it.amount_credit) 107 } 108 // Check conversion from 109 client.getAdmin("$prefix/conversion-info/cashout-rate?amount_credit=EUR:1.257").assertOkJson<ConversionResponse> { 110 assertEquals(TalerAmount("KUDOS:1.01"), it.amount_debit) 111 assertEquals(TalerAmount("EUR:1.257"), it.amount_credit) 112 } 113 114 // Too small 115 client.getAdmin("$prefix/conversion-info/cashout-rate?amount_debit=KUDOS:0.0008") 116 .assertConflict(TalerErrorCode.BANK_BAD_CONVERSION) 117 // No amount 118 client.getAdmin("$prefix/conversion-info/cashout-rate") 119 .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MISSING) 120 // Both amount 121 client.getAdmin("$prefix/conversion-info/cashout-rate?amount_debit=EUR:1&amount_credit=KUDOS:1") 122 .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED) 123 // Wrong format 124 client.getAdmin("$prefix/conversion-info/cashout-rate?amount_debit=1") 125 .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED) 126 client.getAdmin("$prefix/conversion-info/cashout-rate?amount_credit=1") 127 .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED) 128 // Wrong currency 129 client.getAdmin("$prefix/conversion-info/cashout-rate?amount_debit=EUR:1") 130 .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH) 131 client.getAdmin("$prefix/conversion-info/cashout-rate?amount_credit=KUDOS:1") 132 .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH) 133 } 134 client.getA("/accounts/exchange/conversion-info/cashout-rate?amount_debit=KUDOS:1") 135 .assertConflict(TalerErrorCode.BANK_ACCOUNT_IS_EXCHANGE) 136 } 137 138 // GET /conversion-info/cashin-rate 139 @Test 140 fun cashinRate() = bankSetup { 141 authRoutine(HttpMethod.Get, "/conversion-rate-classes/1/conversion-info/cashin-rate?amount_debit=EUR:1", requireAdmin = true) 142 for (prefix in sequenceOf("", "/conversion-rate-classes/1", "/accounts/exchange")) { 143 for ((amount, converted) in listOf( 144 Pair(0.75, 0.58), Pair(0.32, 0.24), Pair(0.66, 0.51) 145 )) { 146 // Check conversion to 147 client.getAdmin("$prefix/conversion-info/cashin-rate?amount_debit=EUR:$amount").assertOkJson<ConversionResponse> { 148 assertEquals(TalerAmount("KUDOS:$converted"), it.amount_credit) 149 assertEquals(TalerAmount("EUR:$amount"), it.amount_debit) 150 } 151 // Check conversion from 152 client.getAdmin("$prefix/conversion-info/cashin-rate?amount_credit=KUDOS:$converted").assertOkJson<ConversionResponse> { 153 assertEquals(TalerAmount("KUDOS:$converted"), it.amount_credit) 154 assertEquals(TalerAmount("EUR:$amount"), it.amount_debit) 155 } 156 } 157 158 // No amount 159 client.getAdmin("$prefix/conversion-info/cashin-rate") 160 .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MISSING) 161 // Both amount 162 client.getAdmin("$prefix/conversion-info/cashin-rate?amount_debit=KUDOS:1&amount_credit=EUR:1") 163 .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED) 164 // Wrong format 165 client.getAdmin("$prefix/conversion-info/cashin-rate?amount_debit=1") 166 .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED) 167 client.getAdmin("$prefix/conversion-info/cashin-rate?amount_credit=1") 168 .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED) 169 // Wrong currency 170 client.getAdmin("$prefix/conversion-info/cashin-rate?amount_debit=KUDOS:1") 171 .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH) 172 client.getAdmin("$prefix/conversion-info/cashin-rate?amount_credit=EUR:1") 173 .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH) 174 } 175 client.get("/accounts/merchant/conversion-info/cashin-rate?amount_debit=EUR:1") 176 .assertConflict(TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE) 177 } 178 179 @Test 180 fun noRate() = bankSetup { db -> 181 db.serializable("DELETE FROM config WHERE key='conversion_rate'") { 182 executeUpdate() 183 } 184 for (prefix in sequenceOf("", "/conversion-rate-classes/1", "/accounts/merchant")) { 185 client.getAdmin("$prefix/conversion-info/config") 186 .assertOkJson<ConversionConfig>() 187 client.getAdmin("$prefix/conversion-info/cashout-rate") 188 .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MISSING) 189 client.getAdmin("$prefix/conversion-info/cashout-rate?amount_credit=EUR:1") 190 .assertConflict(TalerErrorCode.BANK_BAD_CONVERSION) 191 } 192 } 193 194 @Test 195 fun notImplemented() = bankSetup("test_no_conversion.conf") { 196 for (prefix in sequenceOf("", "/conversion-rate-classes/1", "/accounts/merchant")) { 197 client.get("$prefix/conversion-info/cashin-rate") 198 .assertNotImplemented() 199 client.get("$prefix/conversion-info/cashout-rate") 200 .assertNotImplemented() 201 } 202 } 203 }