/* * This file is part of LibEuFin. * Copyright (C) 2024 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3, or * (at your option) any later version. * LibEuFin is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General * Public License for more details. * You should have received a copy of the GNU Affero General Public * License along with LibEuFin; see the file COPYING. If not, see * */ import org.junit.Test import tech.libeufin.bank.DecimalNumber import tech.libeufin.bank.db.TransactionDAO.BankTransactionResult import tech.libeufin.bank.db.WithdrawalDAO.* import tech.libeufin.bank.db.TransactionDAO.* import tech.libeufin.bank.db.CashoutDAO.CashoutCreationResult import tech.libeufin.bank.db.ExchangeDAO.TransferResult import tech.libeufin.common.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import tech.libeufin.bank.* import java.time.* import java.util.* import kotlin.test.* class GcTest { @Test fun gc() = bankSetup { db -> db.conn { conn -> suspend fun assertNb(nb: Int, stmt: String) { assertEquals(nb, conn.prepareStatement(stmt).one { it.getInt(1) }) } suspend fun assertNbAccount(nb: Int) = assertNb(nb, "SELECT count(*) from bank_accounts") suspend fun assertNbTokens(nb: Int) = assertNb(nb, "SELECT count(*) from bearer_tokens") suspend fun assertNbTan(nb: Int) = assertNb(nb, "SELECT count(*) from tan_challenges") suspend fun assertNbCashout(nb: Int) = assertNb(nb, "SELECT count(*) from cashout_operations") suspend fun assertNbWithdrawal(nb: Int) = assertNb(nb, "SELECT count(*) from taler_withdrawal_operations") suspend fun assertNbBankTx(nb: Int) = assertNb(nb, "SELECT count(*) from bank_transaction_operations") suspend fun assertNbTx(nb: Int) = assertNb(nb, "SELECT count(*) from bank_account_transactions") suspend fun assertNbIncoming(nb: Int) = assertNb(nb, "SELECT count(*) from taler_exchange_incoming") suspend fun assertNbOutgoing(nb: Int) = assertNb(nb, "SELECT count(*) from taler_exchange_outgoing") // Time calculation val abortAfter = Duration.ofMinutes(15) val cleanAfter = Period.ofDays(14) val deleteAfter = Period.ofYears(10) val now = Instant.now() val dateTime = LocalDateTime.ofInstant(now, ZoneOffset.UTC) val abort = dateTime.minus(abortAfter).toInstant(ZoneOffset.UTC) val clean = dateTime.minus(cleanAfter).toInstant(ZoneOffset.UTC) val delete = dateTime.minus(deleteAfter).toInstant(ZoneOffset.UTC) // Create test accounts val payto = IbanPayto.rand() val oldPayto = client.post("/accounts") { json { "username" to "old_account" "password" to "old_account-password" "name" to "Old Account" "cashout_payto_uri" to payto } }.assertOkJson().internal_payto_uri val recentPayto = client.post("/accounts") { json { "username" to "recent_account" "password" to "recent_account-password" "name" to "Recent Account" "cashout_payto_uri" to payto } }.assertOkJson().internal_payto_uri assertNbAccount(6) // Create test tokens for (time in listOf(now, clean)) { for (account in listOf("old_account", "recent_account")) { assert(db.token.create(account, ByteArray(32).rand(), time, time, TokenScope.readonly, false)) db.tan.new(account, Operation.cashout, "", "", time, 0, Duration.ZERO, null, null) } } assertNbTokens(4) assertNbTan(4) // Create test operations val from = TalerAmount("KUDOS:1") val to = convert("KUDOS:1") for ((account, times) in listOf( Pair("old_account", listOf(delete)), Pair("recent_account", listOf(now, abort, clean, delete)) )) { for (time in times) { val uuid = UUID.randomUUID() assertEquals( db.withdrawal.create(account, uuid, from, time), WithdrawalCreationResult.Success ) assertIs( db.withdrawal.setDetails(uuid, exchangePayto, EddsaPublicKey.rand()) ) assertEquals( db.withdrawal.confirm(account, uuid, time, false), WithdrawalConfirmationResult.Success ) assertIs( db.cashout.create(account, ShortHashCode.rand(), from, to, "", time, false), ) assertIs( db.transaction.create(customerPayto, account, "", from, time, false, ShortHashCode.rand()), ) } for (time in listOf(now, abort, clean, delete)) { assertEquals( db.withdrawal.create(account, UUID.randomUUID(), from, time), WithdrawalCreationResult.Success ) } } for (time in listOf(now, abort, clean, delete)) { assertIs( db.exchange.transfer( TransferRequest(HashCode.rand(), from, ExchangeUrl("http://localhost"), ShortHashCode.rand(), customerPayto), "exchange", time ) ) } assertNbTx(38) assertNbCashout(5) assertNbBankTx(5) assertNbWithdrawal(13) assertNbIncoming(5) assertNbOutgoing(4) // Check soft delete conn.execSQLUpdate("UPDATE bank_accounts SET balance = (0, 0)::taler_amount") for (account in listOf("old_account", "recent_account")) { client.deleteA("/accounts/$account").assertNoContent() } assertNbAccount(6) db.gc.collect( Instant.now(), abortAfter, cleanAfter, deleteAfter ) // Check hard delete assertNbAccount(5) assertNbTokens(1) assertNbTan(1) assertNbTx(24) assertNbCashout(3) assertNbBankTx(3) assertNbWithdrawal(4) assertNbIncoming(3) assertNbOutgoing(3) }} }