DatabaseTest.kt (4857B)
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.http.* 21 import kotlinx.coroutines.* 22 import org.junit.Test 23 import tech.libeufin.bank.* 24 import tech.libeufin.bank.db.AccountDAO.AccountCreationResult 25 import tech.libeufin.bank.db.TanDAO.* 26 import tech.libeufin.common.* 27 import tech.libeufin.common.assertOk 28 import tech.libeufin.common.db.* 29 import tech.libeufin.common.json 30 import tech.libeufin.common.test.* 31 import java.time.Duration 32 import java.time.Instant 33 import java.time.temporal.ChronoUnit 34 import java.util.UUID 35 import java.util.concurrent.TimeUnit 36 import kotlin.test.assertEquals 37 import kotlin.test.assertIs 38 import kotlin.test.assertNull 39 40 class DatabaseTest { 41 42 // Testing the helper that creates the admin account. 43 @Test 44 fun createAdmin() = setup { db, ctx -> 45 // Create admin account 46 assertIs<AccountCreationResult.Success>(createAdminAccount(db, ctx)) 47 // Checking idempotency 48 assertEquals(AccountCreationResult.UsernameReuse, createAdminAccount(db, ctx)) 49 } 50 51 @Test 52 fun tanChallenge() = bankSetup { db -> db.conn { conn -> 53 val validityPeriod = Duration.ofHours(1) 54 val retransmissionPeriod: Duration = Duration.ofMinutes(1) 55 val retryCounter = 3 56 57 suspend fun create(code: String, timestamp: Instant): UUID { 58 return db.tan.new( 59 hbody = Base32Crockford64B.rand(), 60 salt = Base32Crockford16B.rand(), 61 username = "customer", 62 op = Operation.withdrawal, 63 code = code, 64 timestamp = timestamp, 65 retryCounter = retryCounter, 66 validityPeriod = validityPeriod, 67 tanChannel = TanChannel.sms, 68 tanInfo = "+88" 69 ) 70 } 71 72 suspend fun markSent(id: UUID, timestamp: Instant) { 73 db.tan.markSent(id, timestamp + retransmissionPeriod) 74 } 75 76 suspend fun send(id: UUID, code: String, timestamp: Instant): String? { 77 return (db.tan.send( 78 id, 79 timestamp, 80 10 81 ) as? TanSendResult.Send)?.tanCode 82 } 83 84 val now = Instant.now() 85 val expired = now + validityPeriod 86 val retransmit = now + retransmissionPeriod 87 88 // Check basic 89 create("good-code", now).run { 90 // Bad code 91 assertEquals(TanSolveResult.BadCode, db.tan.solve(this, "bad-code", now)) 92 // Good code 93 assertIs<TanSolveResult.Success>(db.tan.solve(this, "good-code", now)) 94 // Never resend a confirmed challenge 95 assertEquals(TanSendResult.Solved, db.tan.send(this, now, 10)) 96 // Confirmed challenge always ok 97 assertIs<TanSolveResult.Success>(db.tan.solve(this, "good-code", now)) 98 } 99 100 // Check retry 101 create("good-code", now).run { 102 markSent(this, now) 103 // Bad code 104 repeat(retryCounter-1) { 105 assertEquals(TanSolveResult.BadCode, db.tan.solve(this, "bad-code", now)) 106 } 107 assertEquals(TanSolveResult.NoRetry, db.tan.solve(this, "bad-code", now)) 108 // Good code fail 109 assertEquals(TanSolveResult.NoRetry, db.tan.solve(this, "good-code", now)) 110 // New code 111 assertIs<TanSendResult.Success>(db.tan.send(this, now, 10)) 112 } 113 114 // Check retransmission 115 create("good-code", now).run { 116 // Failed to send retransmit 117 assertIs<TanSendResult.Send>(db.tan.send(this, now, 10)) 118 // Code successfully sent and still valid 119 markSent(this, now) 120 assertIs<TanSendResult.Success>(db.tan.send(this, now, 10)) 121 // Code is still valid but should be resent 122 assertIs<TanSendResult.Send>(db.tan.send(this, retransmit, 10)) 123 // Good code fail because expired 124 assertEquals(TanSolveResult.Expired, db.tan.solve(this, "good-code", expired)) 125 // No code because expired 126 assertIs<TanSendResult.Send>(db.tan.send(this, retransmit, 10)) 127 } 128 }} 129 }