libeufin

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

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 }