libeufin

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

commit a4ac514914aa6a823d24b59209448a99bd459cb2
parent 6e8b2bebde52f9f22b0ed8f8399571341b445ed8
Author: Antoine A <>
Date:   Fri,  6 Oct 2023 21:41:37 +0000

Improved database pooling and fix connection closure in tests

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/Database.kt | 221++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mbank/src/test/kotlin/Common.kt | 18------------------
Mbank/src/test/kotlin/DatabaseTest.kt | 53+++++++++++++++++++----------------------------------
Mbank/src/test/kotlin/LibeuFinApiTest.kt | 67+++++++++++++++++--------------------------------------------------
Mbank/src/test/kotlin/TalerApiTest.kt | 22++++++----------------
Mbank/src/test/kotlin/helpers.kt | 29+++++++++++++++++++++++++++++
6 files changed, 188 insertions(+), 222 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt @@ -177,12 +177,11 @@ private fun PreparedStatement.executeUpdateViolation(): Boolean { class Database(dbConfig: String, private val bankCurrency: String): java.io.Closeable { private val dbPool: HikariDataSource + private val jdbcConnStr = getJdbcConnectionFromPg(dbConfig) init { val config = HikariConfig(); - config.jdbcUrl = getJdbcConnectionFromPg(dbConfig) - config.driverClassName = "org.postgresql.Driver" - config.maximumPoolSize = 2 + config.jdbcUrl = jdbcConnStr config.connectionInitSql = "SET search_path TO libeufin_bank;" config.validate() dbPool = HikariDataSource(config); @@ -197,6 +196,13 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos return conn.use(lambda) } + /** Create new connection outside the pool */ + private fun freshConn(): PgConnection { + val conn = DriverManager.getConnection(jdbcConnStr).unwrap(PgConnection::class.java) + conn?.execSQLUpdate("SET search_path TO libeufin_bank;") + return conn + } + // CUSTOMERS /** * This method INSERTs a new customer into the database and @@ -793,133 +799,140 @@ class Database(dbConfig: String, private val bankCurrency: String): java.io.Clos bankAccountId: Long, direction: TransactionDirection, map: (BankAccountTransaction) -> T? - ): List<T> = conn { conn -> - // TODO listening for notification is blocking a connection and postgres support a limited amount of connections - // We should use a single connection to listen for notification and dispatch them with kotlin code - val pg = conn.unwrap(PgConnection::class.java) - val channel = "${direction.name}_$bankAccountId"; + ): List<T> { var start = params.start var delta = params.delta var poll_ms = params.poll_ms; - - val (cmpOp, orderBy) = if (delta < 0) Pair("<", "DESC") else Pair(">", "ASC") - val stmt = conn.prepareStatement(""" - SELECT - creditor_payto_uri - ,creditor_name - ,debtor_payto_uri - ,debtor_name - ,subject - ,(amount).val AS amount_val - ,(amount).frac AS amount_frac - ,transaction_date - ,account_servicer_reference - ,payment_information_id - ,end_to_end_id - ,bank_account_id - ,bank_transaction_id - FROM bank_account_transactions - WHERE bank_transaction_id ${cmpOp} ? - AND bank_account_id=? - AND direction=?::direction_enum - ORDER BY bank_transaction_id ${orderBy} - LIMIT ? - """) + val channel = "${direction.name}_$bankAccountId"; + val items = mutableListOf<T>() // If going backward with a starting point, it is useless to poll if (delta < 0 && start != Long.MAX_VALUE) { poll_ms = 0; } - // Only start expensive listening if we intend to poll + // TODO listening for notification is blocking a connection and postgres support a limited amount of connections + // TODO we should use a single connection to listen for notification and dispatch them with kotlin code + + val conn: Connection; // Generic connection to close and query + val pg: PgConnection; // Postgres connection for notifications + + // Only start expensive listening and connection creation if we intend to poll if (poll_ms > 0) { + pg = freshConn() + conn = pg pg.execSQLUpdate("LISTEN $channel"); + } else { + conn = dbPool.getConnection() + pg = conn.unwrap(PgConnection::class.java) } - val items = mutableListOf<T>() - - fun bankTransactionGetHistory(): List<BankAccountTransaction> { - stmt.setLong(1, start) + conn.use { + // Prepare statement + val (cmpOp, orderBy) = if (delta < 0) Pair("<", "DESC") else Pair(">", "ASC") + val stmt = conn.prepareStatement(""" + SELECT + creditor_payto_uri + ,creditor_name + ,debtor_payto_uri + ,debtor_name + ,subject + ,(amount).val AS amount_val + ,(amount).frac AS amount_frac + ,transaction_date + ,account_servicer_reference + ,payment_information_id + ,end_to_end_id + ,bank_account_id + ,bank_transaction_id + FROM bank_account_transactions + WHERE bank_transaction_id ${cmpOp} ? + AND bank_account_id=? + AND direction=?::direction_enum + ORDER BY bank_transaction_id ${orderBy} + LIMIT ? + """) stmt.setLong(2, bankAccountId) stmt.setString(3, direction.name) - stmt.setLong(4, abs(delta)) - return stmt.all { - BankAccountTransaction( - creditorPaytoUri = it.getString("creditor_payto_uri"), - creditorName = it.getString("creditor_name"), - debtorPaytoUri = it.getString("debtor_payto_uri"), - debtorName = it.getString("debtor_name"), - amount = TalerAmount( - it.getLong("amount_val"), - it.getInt("amount_frac"), - getCurrency() - ), - accountServicerReference = it.getString("account_servicer_reference"), - endToEndId = it.getString("end_to_end_id"), - direction = direction, - bankAccountId = it.getLong("bank_account_id"), - paymentInformationId = it.getString("payment_information_id"), - subject = it.getString("subject"), - transactionDate = it.getLong("transaction_date").microsToJavaInstant() ?: throw faultyTimestampByBank(), - dbRowId = it.getLong("bank_transaction_id") - ) + + fun bankTransactionGetHistory(): List<BankAccountTransaction> { + stmt.setLong(1, start) + stmt.setLong(4, abs(delta)) + return stmt.all { + BankAccountTransaction( + creditorPaytoUri = it.getString("creditor_payto_uri"), + creditorName = it.getString("creditor_name"), + debtorPaytoUri = it.getString("debtor_payto_uri"), + debtorName = it.getString("debtor_name"), + amount = TalerAmount( + it.getLong("amount_val"), + it.getInt("amount_frac"), + getCurrency() + ), + accountServicerReference = it.getString("account_servicer_reference"), + endToEndId = it.getString("end_to_end_id"), + direction = direction, + bankAccountId = it.getLong("bank_account_id"), + paymentInformationId = it.getString("payment_information_id"), + subject = it.getString("subject"), + transactionDate = it.getLong("transaction_date").microsToJavaInstant() ?: throw faultyTimestampByBank(), + dbRowId = it.getLong("bank_transaction_id") + ) + } } - } - fun loadBankHistory() { - while (delta != 0L) { - val history = bankTransactionGetHistory() - if (history.isEmpty()) - break; - history.forEach { - val item = map(it); - // Advance cursor - start = it.expectRowId() - - if (item != null) { - items.add(item) - // Reduce delta - if (delta < 0) delta++ else delta--; + fun loadBankHistory() { + while (delta != 0L) { + val history = bankTransactionGetHistory() + if (history.isEmpty()) + break; + history.forEach { + val item = map(it); + // Advance cursor + start = it.expectRowId() + + if (item != null) { + items.add(item) + // Reduce delta + if (delta < 0) delta++ else delta--; + } } } } - } - loadBankHistory() - - // Long polling - while (delta != 0L && poll_ms > 0) { - var remaining = abs(delta); - do { - val pollStart = System.currentTimeMillis() - logger.debug("POOL") - pg.getNotifications(poll_ms.toInt()).forEach { - val id = it.parameter.toLong() - val new = when { - params.start == Long.MAX_VALUE -> true - delta < 0 -> id < start - else -> id > start + loadBankHistory() + + // Long polling + while (delta != 0L && poll_ms > 0) { + var remaining = abs(delta); + do { + val pollStart = System.currentTimeMillis() + pg.getNotifications(poll_ms.toInt()).forEach { + val id = it.parameter.toLong() + val new = when { + params.start == Long.MAX_VALUE -> true + delta < 0 -> id < start + else -> id > start + } + if (new) remaining -= 1 } - logger.debug("NOTIF $id $new") - if (new) remaining -= 1 + val pollEnd = System.currentTimeMillis() + poll_ms -= pollEnd - pollStart + } while (poll_ms > 0 && remaining > 0L) + + // If going backward without a starting point, we reset loading progress + if (params.start == Long.MAX_VALUE) { + start = params.start + delta = params.delta + items.clear() } - val pollEnd = System.currentTimeMillis() - poll_ms -= pollEnd - pollStart - } while (poll_ms > 0 && remaining > 0L) - - // If going backward without a starting point, we reset loading progress - if (params.start == Long.MAX_VALUE) { - start = params.start - delta = params.delta - items.clear() + loadBankHistory() } - loadBankHistory() - } - pg.execSQLUpdate("UNLISTEN $channel"); - pg.getNotifications(); // Clear pending notifications + // No need to unlisten or clear notifications as we close the connection when polling is used - items.toList() + return items.toList(); + } } /** diff --git a/bank/src/test/kotlin/Common.kt b/bank/src/test/kotlin/Common.kt @@ -35,24 +35,6 @@ fun initDb(): Database { return Database(dbConnStr, "KUDOS") } -fun getTestContext( - restrictRegistration: Boolean = false, - suggestedExchange: String = "https://exchange.example.com" -): BankApplicationContext { - return BankApplicationContext( - currency = "KUDOS", - restrictRegistration = restrictRegistration, - cashoutCurrency = "EUR", - defaultCustomerDebtLimit = TalerAmount(100, 0, "KUDOS"), - defaultAdminDebtLimit = TalerAmount(10000, 0, "KUDOS"), - registrationBonusEnabled = false, - registrationBonus = null, - suggestedWithdrawalExchange = suggestedExchange, - spaCaptchaURL = null, - restrictAccountDeletion = true - ) -} - fun deflater(reqBody: String): ByteArray { val bos = ByteArrayOutputStream() val ios = DeflaterOutputStream(bos) diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt @@ -85,9 +85,7 @@ class DatabaseTest { // Testing the helper that creates the admin account. @Test - fun createAdminTest() { - val db = initDb() - val ctx = getTestContext() + fun createAdminTest() = setup { db, ctx -> // No admin accounts is expected. val noAdminCustomer = db.customerGetFromLogin("admin") assert(noAdminCustomer == null) @@ -112,7 +110,7 @@ class DatabaseTest { * given by the exchange to pay one merchant. */ @Test - fun talerTransferTest() { + fun talerTransferTest() = setupDb { db -> val exchangeReq = TransferRequest( amount = TalerAmount(9, 0, "KUDOS"), credit_account = "payto://iban/BAR-IBAN-ABC".lowercase(), // foo pays bar @@ -120,7 +118,6 @@ class DatabaseTest { request_uid = randHashCode(), wtid = randShortHashCode() ) - val db = initDb() val fooId = db.customerCreate(customerFoo) assert(fooId != null) val barId = db.customerCreate(customerBar) @@ -136,8 +133,7 @@ class DatabaseTest { } @Test - fun bearerTokenTest() { - val db = initDb() + fun bearerTokenTest() = setupDb { db -> val tokenBytes = ByteArray(32) Random().nextBytes(tokenBytes) val token = BearerToken( @@ -154,8 +150,7 @@ class DatabaseTest { } @Test - fun tokenDeletionTest() { - val db = initDb() + fun tokenDeletionTest() = setupDb { db -> val token = ByteArray(32) // Token not there, must fail. assert(!db.bearerTokenDelete(token)) @@ -179,8 +174,7 @@ class DatabaseTest { } @Test - fun bankTransactionsTest() { - val db = initDb() + fun bankTransactionsTest() = setupDb { db -> val fooId = db.customerCreate(customerFoo) assert(fooId != null) val barId = db.customerCreate(customerBar) @@ -262,8 +256,7 @@ class DatabaseTest { // Testing customer(+bank account) deletion logic. @Test - fun customerDeletionTest() { - val db = initDb() + fun customerDeletionTest() = setupDb { db -> // asserting false, as foo doesn't exist yet. assert(db.customerDeleteIfBalanceIsZero("foo") == CustomerDeletionResult.CUSTOMER_NOT_FOUND) // Creating foo. @@ -273,11 +266,11 @@ class DatabaseTest { } // foo has zero balance, deletion should succeed. assert(db.customerDeleteIfBalanceIsZero("foo") == CustomerDeletionResult.SUCCESS) - val db2 = initDb() + // Creating foo again, artificially setting its balance != zero. - db2.customerCreate(customerFoo).apply { + db.customerCreate(customerFoo).apply { assert(this != null) - db2.bankAccountCreate(bankAccountFoo).apply { + db.bankAccountCreate(bankAccountFoo).apply { assert(this != null) val conn = DriverManager.getConnection("jdbc:postgresql:///libeufincheck").unwrap(PgConnection::class.java) conn.execSQLUpdate("UPDATE libeufin_bank.bank_accounts SET balance.frac = 1 WHERE bank_account_id = $this") @@ -285,9 +278,9 @@ class DatabaseTest { } assert(db.customerDeleteIfBalanceIsZero("foo") == CustomerDeletionResult.BALANCE_NOT_ZERO) } + @Test - fun customerCreationTest() { - val db = initDb() + fun customerCreationTest() = setupDb { db -> assert(db.customerGetFromLogin("foo") == null) db.customerCreate(customerFoo) assert(db.customerGetFromLogin("foo")?.name == "Foo") @@ -296,8 +289,7 @@ class DatabaseTest { } @Test - fun bankAccountTest() { - val db = initDb() + fun bankAccountTest() = setupDb { db -> val currency = "KUDOS" assert(db.bankAccountGetFromOwnerId(1L) == null) assert(db.customerCreate(customerFoo) != null) @@ -307,8 +299,7 @@ class DatabaseTest { } @Test - fun withdrawalTest() { - val db = initDb() + fun withdrawalTest() = setupDb { db -> val uuid = UUID.randomUUID() val currency = "KUDOS" assert(db.customerCreate(customerFoo) != null) @@ -338,8 +329,7 @@ class DatabaseTest { } // Only testing the interaction between Kotlin and the DBMS. No actual logic tested. @Test - fun historyTest() { - val db = initDb() + fun historyTest() = setupDb { db -> val currency = "KUDOS" db.customerCreate(customerFoo); db.bankAccountCreate(bankAccountFoo) db.customerCreate(customerBar); db.bankAccountCreate(bankAccountBar) @@ -361,8 +351,7 @@ class DatabaseTest { assert(backward[0].expectRowId() <= 50 && backward.size == 2 && backward[0].dbRowId!! > backward[1].dbRowId!!) } @Test - fun cashoutTest() { - val db = initDb() + fun cashoutTest() = setupDb { db -> val currency = "KUDOS" val op = Cashout( cashoutUuid = UUID.randomUUID(), @@ -417,8 +406,7 @@ class DatabaseTest { // Tests the retrieval of many accounts, used along GET /accounts @Test - fun accountsForAdminTest() { - val db = initDb() + fun accountsForAdminTest() = setupDb { db -> assert(db.accountsGetForAdmin().isEmpty()) // No data exists yet. assert(db.customerCreate(customerFoo) != null) assert(db.bankAccountCreate(bankAccountFoo) != null) @@ -430,8 +418,7 @@ class DatabaseTest { } @Test - fun passwordChangeTest() { - val db = initDb() + fun passwordChangeTest() = setupDb { db -> // foo not found, this fails. assert(!db.customerChangePassword("foo", "won't make it")) // creating foo. @@ -441,8 +428,7 @@ class DatabaseTest { } @Test - fun getPublicAccountsTest() { - val db = initDb() + fun getPublicAccountsTest() = setupDb { db -> // Expecting empty, no accounts exist yet. assert(db.accountsGetPublic("KUDOS").isEmpty()) // Make a NON-public account, so expecting still an empty result. @@ -477,8 +463,7 @@ class DatabaseTest { * PATCH /accounts/foo endpoint. */ @Test - fun accountReconfigTest() { - val db = initDb() + fun accountReconfigTest() = setupDb { db -> // asserting for the customer not being found. db.accountReconfig( "foo", diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt b/bank/src/test/kotlin/LibeuFinApiTest.kt @@ -49,9 +49,7 @@ class LibeuFinApiTest { ) @Test - fun getConfig() { - val db = initDb() - val ctx = getTestContext() + fun getConfig() = setup { db, ctx -> testApplication { application { corebankWebApp(db, ctx) } val r = client.get("/config") { @@ -59,6 +57,7 @@ class LibeuFinApiTest { } println(r.bodyAsText()) } + } /** @@ -68,9 +67,7 @@ class LibeuFinApiTest { * of delta. */ @Test - fun testHistory() { - val db = initDb() - val ctx = getTestContext() + fun testHistory() = setup { db, ctx -> val fooId = db.customerCreate(customerFoo); assert(fooId != null) assert(db.bankAccountCreate(genBankAccount(fooId!!)) != null) val barId = db.customerCreate(customerBar); assert(barId != null) @@ -101,9 +98,7 @@ class LibeuFinApiTest { // Testing the creation of bank transactions. @Test - fun postTransactionsTest() { - val db = initDb() - val ctx = getTestContext() + fun postTransactionsTest() = setup { db, ctx -> // foo account val fooId = db.customerCreate(customerFoo); assert(fooId != null) @@ -185,9 +180,7 @@ class LibeuFinApiTest { } @Test - fun passwordChangeTest() { - val db = initDb() - val ctx = getTestContext() + fun passwordChangeTest() = setup { db, ctx -> assert(db.customerCreate(customerFoo) != null) testApplication { application { @@ -219,9 +212,7 @@ class LibeuFinApiTest { } } @Test - fun tokenDeletionTest() { - val db = initDb() - val ctx = getTestContext() + fun tokenDeletionTest() = setup { db, ctx -> assert(db.customerCreate(customerFoo) != null) val token = ByteArray(32) Random.nextBytes(token) @@ -269,9 +260,7 @@ class LibeuFinApiTest { } @Test - fun publicAccountsTest() { - val db = initDb() - val ctx = getTestContext() + fun publicAccountsTest() = setup { db, ctx -> testApplication { application { corebankWebApp(db, ctx) @@ -305,9 +294,7 @@ class LibeuFinApiTest { } // Creating token with "forever" duration. @Test - fun tokenForeverTest() { - val db = initDb() - val ctx = getTestContext() + fun tokenForeverTest() = setup { db, ctx -> assert(db.customerCreate(customerFoo) != null) testApplication { application { @@ -330,9 +317,7 @@ class LibeuFinApiTest { // Testing that too big or invalid durations fail the request. @Test - fun tokenInvalidDurationTest() { - val db = initDb() - val ctx = getTestContext() + fun tokenInvalidDurationTest() = setup { db, ctx -> assert(db.customerCreate(customerFoo) != null) testApplication { application { @@ -369,9 +354,7 @@ class LibeuFinApiTest { } // Checking the POST /token handling. @Test - fun tokenTest() { - val db = initDb() - val ctx = getTestContext() + fun tokenTest() = setup { db, ctx -> assert(db.customerCreate(customerFoo) != null) testApplication { application { @@ -441,10 +424,8 @@ class LibeuFinApiTest { * to show customers their status. */ @Test - fun getAccountTest() { + fun getAccountTest() = setup { db, ctx -> // Artificially insert a customer and bank account in the database. - val db = initDb() - val ctx = getTestContext() val customerRowId = db.customerCreate( Customer( "foo", @@ -508,10 +489,8 @@ class LibeuFinApiTest { * Testing the account creation and its idempotency */ @Test - fun createAccountTest() { + fun createAccountTest() = setup { db, ctx -> testApplication { - val db = initDb() - val ctx = getTestContext() val ibanPayto = genIbanPaytoUri() application { corebankWebApp(db, ctx) @@ -552,10 +531,8 @@ class LibeuFinApiTest { * Testing the account creation and its idempotency */ @Test - fun createTwoAccountsTest() { + fun createTwoAccountsTest() = setup { db, ctx -> testApplication { - val db = initDb() - val ctx = getTestContext() application { corebankWebApp(db, ctx) } @@ -591,12 +568,8 @@ class LibeuFinApiTest { * Test admin-only account creation */ @Test - fun createAccountRestrictedTest() { + fun createAccountRestrictedTest() = setup(restrictRegistration = true) { db, ctx -> testApplication { - val db = initDb() - // For this test, we restrict registrations - val ctx = getTestContext(restrictRegistration = true) - application { corebankWebApp(db, ctx) } @@ -647,9 +620,7 @@ class LibeuFinApiTest { * Tests DELETE /accounts/foo */ @Test - fun deleteAccount() { - val db = initDb() - val ctx = getTestContext() + fun deleteAccount() = setup { db, ctx -> val adminCustomer = Customer( "admin", CryptoUtil.hashpw("pass"), @@ -714,9 +685,7 @@ class LibeuFinApiTest { * Tests reconfiguration of account data. */ @Test - fun accountReconfig() { - val db = initDb() - val ctx = getTestContext() + fun accountReconfig() = setup { db, ctx -> testApplication { application { corebankWebApp(db, ctx) @@ -779,9 +748,7 @@ class LibeuFinApiTest { * Tests the GET /accounts endpoint. */ @Test - fun getAccountsList() { - val db = initDb() - val ctx = getTestContext() + fun getAccountsList() = setup { db, ctx -> val adminCustomer = Customer( "admin", CryptoUtil.hashpw("pass"), diff --git a/bank/src/test/kotlin/TalerApiTest.kt b/bank/src/test/kotlin/TalerApiTest.kt @@ -67,10 +67,8 @@ class TalerApiTest { ) } - fun commonSetup(lambda: (Database, BankApplicationContext) -> Unit){ - val db = initDb() - val ctx = getTestContext() - db.use { + fun commonSetup(lambda: (Database, BankApplicationContext) -> Unit) { + setup { db, ctx -> // Creating the exchange and merchant accounts first. assertNotNull(db.customerCreate(customerFoo)) assertNotNull(db.bankAccountCreate(bankAccountFoo)) @@ -533,9 +531,7 @@ class TalerApiTest { } // Selecting withdrawal details from the Integration API endpoint. @Test - fun intSelect() { - val db = initDb() - val ctx = getTestContext(suggestedExchange = "payto://iban/ABC123") + fun intSelect() = setup(suggestedExchange = "payto://iban/ABC123") { db, ctx -> val uuid = UUID.randomUUID() assertNotNull(db.customerCreate(customerFoo)) assertNotNull(db.bankAccountCreate(bankAccountFoo)) @@ -560,12 +556,10 @@ class TalerApiTest { } // Showing withdrawal details from the Integrtion API endpoint. @Test - fun intGet() { - val db = initDb() + fun intGet() = setup(suggestedExchange = "payto://iban/ABC123") { db, ctx -> val uuid = UUID.randomUUID() assert(db.customerCreate(customerFoo) != null) assert(db.bankAccountCreate(bankAccountFoo) != null) - val ctx = getTestContext(suggestedExchange = "payto://iban/ABC123") // insert new. assert(db.talerWithdrawalCreate( opUUID = uuid, @@ -582,10 +576,8 @@ class TalerApiTest { } // Testing withdrawal abort @Test - fun withdrawalAbort() { - val db = initDb() + fun withdrawalAbort() = setup { db, ctx -> val uuid = UUID.randomUUID() - val ctx = getTestContext() assert(db.customerCreate(customerFoo) != null) assert(db.bankAccountCreate(bankAccountFoo) != null) // insert new. @@ -610,9 +602,7 @@ class TalerApiTest { } // Testing withdrawal creation @Test - fun withdrawalCreation() { - val db = initDb() - val ctx = getTestContext() + fun withdrawalCreation() = setup { db, ctx -> assertNotNull(db.customerCreate(customerFoo)) assertNotNull(db.bankAccountCreate(bankAccountFoo)) testApplication { diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt @@ -10,6 +10,35 @@ import net.taler.wallet.crypto.Base32Crockford import kotlin.test.assertEquals import tech.libeufin.bank.* +/* ----- Setup ----- */ + +fun setupDb(lambda: (Database) -> Unit) { + initDb().use(lambda) +} + +fun setup( + restrictRegistration: Boolean = false, + suggestedExchange: String = "https://exchange.example.com", + lambda: (Database, BankApplicationContext) -> Unit +){ + val db = initDb() + val ctx = BankApplicationContext( + currency = "KUDOS", + restrictRegistration = restrictRegistration, + cashoutCurrency = "EUR", + defaultCustomerDebtLimit = TalerAmount(100, 0, "KUDOS"), + defaultAdminDebtLimit = TalerAmount(10000, 0, "KUDOS"), + registrationBonusEnabled = false, + registrationBonus = null, + suggestedWithdrawalExchange = suggestedExchange, + spaCaptchaURL = null, + restrictAccountDeletion = true + ) + db.use { + lambda(db, ctx) + } +} + /* ----- Assert ----- */ fun HttpResponse.assertStatus(status: HttpStatusCode): HttpResponse {