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:
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 {