GcTest.kt (6884B)
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.request.* 21 import org.junit.Test 22 import tech.libeufin.bank.* 23 import tech.libeufin.bank.db.CashoutDAO.CashoutCreationResult 24 import tech.libeufin.bank.db.ExchangeDAO.TransferResult 25 import tech.libeufin.bank.db.TransactionDAO.BankTransactionResult 26 import tech.libeufin.bank.db.WithdrawalDAO.* 27 import tech.libeufin.common.* 28 import tech.libeufin.common.test.* 29 import tech.libeufin.common.db.* 30 import java.time.Duration 31 import java.time.Instant 32 import java.util.* 33 import kotlin.test.assertEquals 34 import kotlin.test.assertIs 35 36 class GcTest { 37 @Test 38 fun gc() = bankSetup { db -> db.conn { conn -> 39 fun assertNb(nb: Int, stmt: String) { 40 assertEquals(nb, conn.talerStatement(stmt).one { it.getInt(1) }) 41 } 42 fun assertNbAccount(nb: Int) = assertNb(nb, "SELECT count(*) from bank_accounts") 43 fun assertNbTokens(nb: Int) = assertNb(nb, "SELECT count(*) from bearer_tokens") 44 fun assertNbTan(nb: Int) = assertNb(nb, "SELECT count(*) from tan_challenges") 45 fun assertNbCashout(nb: Int) = assertNb(nb, "SELECT count(*) from cashout_operations") 46 fun assertNbWithdrawal(nb: Int) = assertNb(nb, "SELECT count(*) from taler_withdrawal_operations") 47 fun assertNbBankTx(nb: Int) = assertNb(nb, "SELECT count(*) from bank_transaction_operations") 48 fun assertNbTx(nb: Int) = assertNb(nb, "SELECT count(*) from bank_account_transactions") 49 fun assertNbIncoming(nb: Int) = assertNb(nb, "SELECT count(*) from taler_exchange_incoming") 50 fun assertNbOutgoing(nb: Int) = assertNb(nb, "SELECT count(*) from taler_exchange_outgoing") 51 52 val ZERO = TalerAmount.zero("KUDOS") 53 val MAX = TalerAmount.max("KUDOS") 54 55 // Time calculation 56 val abortAfter = Duration.ofMinutes(15) 57 val cleanAfter = Duration.ofDays(14) 58 val deleteAfter = Duration.ofDays(350) 59 val now = Instant.now() 60 val abort = now.minus(abortAfter) 61 val clean = now.minus(cleanAfter) 62 val delete = now.minus(deleteAfter) 63 64 // Create test accounts 65 val payto = IbanPayto.rand() 66 client.post("/accounts") { 67 json { 68 "username" to "old_account" 69 "password" to "old_account-password" 70 "name" to "Old Account" 71 "cashout_payto_uri" to payto 72 } 73 }.assertOkJson<RegisterAccountResponse>().internal_payto_uri 74 client.post("/accounts") { 75 json { 76 "username" to "recent_account" 77 "password" to "recent_account-password" 78 "name" to "Recent Account" 79 "cashout_payto_uri" to payto 80 } 81 82 }.assertOkJson<RegisterAccountResponse>().internal_payto_uri 83 assertNbAccount(6) 84 85 // Create test tokens 86 for (time in listOf(now, clean)) { 87 for (account in listOf("old_account", "recent_account")) { 88 db.token.create(account, ByteArray(32).rand(), time, time, TokenScope.readonly, false, null, true) 89 db.tan.new(account, Operation.cashout, Base32Crockford64B.rand(), Base32Crockford16B.rand(), "", time, 0, Duration.ZERO, TanChannel.sms, "") 90 } 91 } 92 assertNbTokens(5) 93 assertNbTan(4) 94 95 // Create test operations 96 val from = TalerAmount("KUDOS:1") 97 val to = convert("KUDOS:1") 98 for ((account, times) in listOf( 99 Pair("old_account", listOf(delete)), 100 Pair("recent_account", listOf(now, abort, clean, delete)) 101 )) { 102 for (time in times) { 103 val uuid = UUID.randomUUID() 104 assertEquals( 105 db.withdrawal.create(account, uuid, from, null, false, time, ZERO, ZERO, MAX), 106 WithdrawalCreationResult.Success 107 ) 108 assertIs<WithdrawalSelectionResult.Success>( 109 db.withdrawal.setDetails(uuid, exchangePayto, EddsaPublicKey.rand(), null, ZERO, ZERO, MAX) 110 ) 111 assertEquals( 112 db.withdrawal.confirm(account, uuid, time, null, false, ZERO, ZERO, MAX), 113 WithdrawalConfirmationResult.Success 114 ) 115 assertIs<CashoutCreationResult.Success>( 116 db.cashout.create(account, ShortHashCode.rand(), from, to, "", time, false), 117 ) 118 assertIs<BankTransactionResult.Success>( 119 db.transaction.create(customerPayto, account, "", from, time, false, ShortHashCode.rand(), ZERO, ZERO, MAX), 120 ) 121 } 122 for (time in listOf(now, abort, clean, delete)) { 123 assertEquals( 124 db.withdrawal.create(account, UUID.randomUUID(), from, null, false, time, ZERO, ZERO, MAX), 125 WithdrawalCreationResult.Success 126 ) 127 } 128 } 129 for (time in listOf(now, abort, clean, delete)) { 130 assertIs<TransferResult.Success>( 131 db.exchange.transfer( 132 TransferRequest(HashCode.rand(), from, BaseURL.parse("http://localhost/"), ShortHashCode.rand(), customerPayto), 133 "exchange", time, false 134 ) 135 ) 136 } 137 assertNbTx(38) 138 assertNbCashout(5) 139 assertNbBankTx(5) 140 assertNbWithdrawal(13) 141 assertNbIncoming(5) 142 assertNbOutgoing(4) 143 144 // Check soft delete 145 conn.execSQLUpdate("UPDATE bank_accounts SET balance = (0, 0)::taler_amount") 146 for (account in listOf("old_account", "recent_account")) { 147 client.deleteA("/accounts/$account").assertNoContent() 148 } 149 assertNbAccount(6) 150 151 db.gc.collect( 152 Instant.now(), 153 abortAfter, 154 cleanAfter, 155 deleteAfter 156 ) 157 // Check hard delete 158 assertNbAccount(5) 159 assertNbTokens(3) 160 assertNbTan(1) 161 assertNbTx(24) 162 assertNbCashout(3) 163 assertNbBankTx(3) 164 assertNbWithdrawal(4) 165 assertNbIncoming(3) 166 assertNbOutgoing(3) 167 }} 168 }