gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated (297228f9 -> f803d69f)


From: gnunet
Subject: [libeufin] branch master updated (297228f9 -> f803d69f)
Date: Tue, 24 Oct 2023 10:57:47 +0200

This is an automated email from the git hooks/post-receive script.

ms pushed a change to branch master
in repository libeufin.

    from 297228f9 Fix /monitor
     new 553ce68a nexus db: creating & getting payment initiations.
     new 48d2df00 nexus db: switching submitted state of payments.
     new f803d69f Nexus database.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .idea/kotlinc.xml                                  |   6 -
 Makefile                                           |   1 -
 contrib/libeufin-nexus.conf                        |   3 +
 database-versioning/libeufin-nexus-0001.sql        |   4 +-
 .../main/kotlin/tech/libeufin/nexus/Database.kt    | 172 +++++++++++++++++++--
 .../src/main/kotlin/tech/libeufin/nexus/DbInit.kt  |  43 ++++++
 .../main/kotlin/tech/libeufin/nexus/EbicsSetup.kt  |  14 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |  42 +++--
 nexus/src/test/kotlin/Common.kt                    |  25 ++-
 nexus/src/test/kotlin/DatabaseTest.kt              | 111 ++++++++++++-
 nexus/src/test/kotlin/Keys.kt                      |   4 +-
 util/src/main/kotlin/DB.kt                         |   9 +-
 12 files changed, 378 insertions(+), 56 deletions(-)
 delete mode 100644 .idea/kotlinc.xml
 create mode 100644 nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt

diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index 4251b727..00000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="KotlinJpsPluginSettings">
-    <option name="version" value="1.7.22" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 571125d9..9a6612e2 100644
--- a/Makefile
+++ b/Makefile
@@ -53,7 +53,6 @@ install-nexus:
        install contrib/libeufin-nexus.conf $(nexus_config_dir)/
        install -D database-versioning/libeufin-nexus*.sql -t $(nexus_sql_dir)
        install -D database-versioning/versioning.sql -t $(nexus_sql_dir)
-       install -D database-versioning/procedures.sql -t $(nexus_sql_dir)
        ./gradlew -q -Pprefix=$(abs_destdir)$(prefix) nexus:installToPrefix
 
 .PHONY: assemble
diff --git a/contrib/libeufin-nexus.conf b/contrib/libeufin-nexus.conf
index 16153c81..5db6b00b 100644
--- a/contrib/libeufin-nexus.conf
+++ b/contrib/libeufin-nexus.conf
@@ -38,6 +38,9 @@ BANK_DIALECT = postfinance
 [nexus-postgres]
 CONFIG = postgres:///libeufin-nexus
 
+[libeufin-nexusdb-postgres]
+SQL_DIR = $DATADIR/sql/
+
 [nexus-ebics-fetch]
 FREQUENCY = 30s # used when long-polling is not supported
 STATEMENT_LOG_DIRECTORY = /tmp/ebics-messages/
diff --git a/database-versioning/libeufin-nexus-0001.sql 
b/database-versioning/libeufin-nexus-0001.sql
index 39f3bf5c..003bd423 100644
--- a/database-versioning/libeufin-nexus-0001.sql
+++ b/database-versioning/libeufin-nexus-0001.sql
@@ -51,14 +51,14 @@ CREATE TABLE IF NOT EXISTS initiated_outgoing_transactions
   (initiated_outgoing_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY 
UNIQUE -- used as our ID in PAIN
   ,amount taler_amount NOT NULL
   ,wire_transfer_subject TEXT
-  ,execution_time INT8 NOT NULL
+  ,initiation_time INT8 NOT NULL
   ,credit_payto_uri TEXT NOT NULL
   ,outgoing_transaction_id INT8 REFERENCES outgoing_transactions 
(outgoing_transaction_id)
   ,submitted BOOL DEFAULT FALSE 
   ,hidden BOOL DEFAULT FALSE -- FIXME: explain this.
   ,client_request_uuid TEXT UNIQUE
   ,failure_message TEXT -- NOTE: that may mix soon failures (those found at 
initiation time), or late failures (those found out along a fetch operation)
-    );
+  );
 
 COMMENT ON COLUMN initiated_outgoing_transactions.outgoing_transaction_id
     IS 'Points to the bank transaction that was found via nexus-fetch.  If 
"submitted" is false or nexus-fetch could not download this initiation, this 
column is expected to be NULL.';
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
index 9e15ad89..77086257 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
@@ -5,20 +5,35 @@ import kotlinx.coroutines.withContext
 import org.postgresql.jdbc.PgConnection
 import tech.libeufin.util.pgDataSource
 import com.zaxxer.hikari.*
+import tech.libeufin.util.microsToJavaInstant
 import tech.libeufin.util.stripIbanPayto
 import tech.libeufin.util.toDbMicros
 import java.sql.PreparedStatement
 import java.sql.SQLException
 import java.time.Instant
 
-/* only importing TalerAmount from bank ONCE that Nexus has
-* its httpd component.  */
 data class TalerAmount(
     val value: Long,
     val fraction: Int,
     val currency: String
 )
 
+// INCOMING PAYMENTS STRUCTS
+
+/**
+ * Represents an incoming payment in the database.
+ */
+data class IncomingPayment(
+    val amount: TalerAmount,
+    val wireTransferSubject: String,
+    val debitPaytoUri: String,
+    val executionTime: Instant,
+    val bankTransferId: String,
+    val bounced: Boolean
+)
+
+// INITIATED PAYMENTS STRUCTS
+
 /**
  * Minimal set of information to initiate a new payment in
  * the database.
@@ -26,8 +41,8 @@ data class TalerAmount(
 data class InitiatedPayment(
     val amount: TalerAmount,
     val wireTransferSubject: String,
-    val executionTime: Instant,
     val creditPaytoUri: String,
+    val initiationTime: Instant,
     val clientRequestUuid: String? = null
 )
 
@@ -36,7 +51,6 @@ data class InitiatedPayment(
  * into the database.
  */
 enum class PaymentInitiationOutcome {
-    BAD_TIMESTAMP,
     BAD_CREDIT_PAYTO,
     UNIQUE_CONSTRAINT_VIOLATION,
     SUCCESS
@@ -45,8 +59,9 @@ enum class PaymentInitiationOutcome {
 /**
  * Performs a INSERT, UPDATE, or DELETE operation.
  *
- * @return true on success, false on unique constraint violation,
- *         rethrows on any other issue.
+ * @return true if at least one row was affected by this operation,
+ *         false on unique constraint violation or no rows were affected.
+ *
  */
 private fun PreparedStatement.maybeUpdate(): Boolean {
     try {
@@ -56,7 +71,7 @@ private fun PreparedStatement.maybeUpdate(): Boolean {
         if (e.sqlState == "23505") return false // unique_violation
         throw e // rethrowing, not to hide other types of errors.
     }
-    return true
+    return updateCount > 0
 }
 
 /**
@@ -96,43 +111,166 @@ class Database(dbConfig: String): java.io.Closeable {
         }
     }
 
+    // INCOMING PAYMENTS METHODS
+
+    /**
+     * Flags an incoming payment as bounced.  NOTE: the flag merely means
+     * that the payment had an invalid subject for a Taler withdrawal _and_
+     * it got sent to the initiated outgoing payments.  In NO way this flag
+     * means that the actual value was returned to the initial debtor.
+     *
+     * FIXME: this needs to run within the same transaction where the payment 
gets initiated.
+     *
+     * @param rowId row ID of the payment to flag as bounced.
+     * @return true on success, false otherwise.
+     */
+    suspend fun incomingPaymentSetAsBounced(rowId: Long): Boolean = runConn { 
conn ->
+        val stmt = conn.prepareStatement("""
+             UPDATE incoming_transactions
+                      SET bounced = true
+                      WHERE incoming_transaction_id=?
+             """
+        )
+        stmt.setLong(1, rowId)
+        return@runConn stmt.maybeUpdate()
+    }
+
+    /**
+     * Creates a new incoming payment record in the database.
+     *
+     * @param paymentData information related to the incoming payment.
+     * @return true on success, false otherwise.
+     */
+    suspend fun incomingPaymentCreate(paymentData: IncomingPayment): Boolean = 
runConn { conn ->
+        val stmt = conn.prepareStatement("""
+            INSERT INTO incoming_transactions (
+              amount
+              ,wire_transfer_subject
+              ,execution_time
+              ,debit_payto_uri
+              ,bank_transfer_id
+              ,bounced
+            ) VALUES (
+              (?,?)::taler_amount
+              ,?
+              ,?
+              ,?
+              ,?
+              ,?
+            )
+        """)
+        stmt.setLong(1, paymentData.amount.value)
+        stmt.setInt(2, paymentData.amount.fraction)
+        stmt.setString(3, paymentData.wireTransferSubject)
+        val executionTime = paymentData.executionTime.toDbMicros() ?: run {
+            throw Exception("Execution time could not be converted to 
microseconds for the database.")
+        }
+        stmt.setLong(4, executionTime)
+        stmt.setString(5, paymentData.debitPaytoUri)
+        stmt.setString(6, paymentData.bankTransferId)
+        stmt.setBoolean(7, paymentData.bounced)
+        return@runConn stmt.maybeUpdate()
+    }
+
+    // INITIATED PAYMENTS METHODS
+
+    /**
+     * Sets payment initiation as submitted.
+     *
+     * @param rowId row ID of the record to set.
+     * @return true on success, false if no payment was affected.
+     */
+    suspend fun initiatedPaymentSetSubmitted(rowId: Long): Boolean = runConn { 
conn ->
+        val stmt = conn.prepareStatement("""
+             UPDATE initiated_outgoing_transactions
+                      SET submitted = true
+                      WHERE initiated_outgoing_transaction_id=?
+             """
+        )
+        stmt.setLong(1, rowId)
+        return@runConn stmt.maybeUpdate()
+    }
+
+    /**
+     * Gets any initiated payment that was not submitted to the
+     * bank yet.
+     *
+     * @param currency in which currency should the payment be submitted to 
the bank.
+     * @return potentially empty list of initiated payments.
+     */
+    suspend fun initiatedPaymentsUnsubmittedGet(currency: String): Map<Long, 
InitiatedPayment> = runConn { conn ->
+        val stmt = conn.prepareStatement("""
+            SELECT
+              initiated_outgoing_transaction_id
+             ,(amount).val as amount_val
+             ,(amount).frac as amount_frac
+             ,wire_transfer_subject
+             ,credit_payto_uri
+             ,initiation_time
+             ,client_request_uuid
+             FROM initiated_outgoing_transactions
+             WHERE submitted=false;
+        """)
+        val maybeMap = mutableMapOf<Long, InitiatedPayment>()
+        stmt.executeQuery().use {
+            if (!it.next()) return@use
+            do {
+                val rowId = it.getLong("initiated_outgoing_transaction_id")
+                val initiationTime = 
it.getLong("initiation_time").microsToJavaInstant()
+                if (initiationTime == null) { // nexus fault
+                    throw Exception("Found invalid timestamp at initiated 
payment with ID: $rowId")
+                }
+                maybeMap[rowId] = InitiatedPayment(
+                    amount = TalerAmount(
+                        value = it.getLong("amount_val"),
+                        fraction = it.getInt("amount_frac"),
+                        currency = currency
+                    ),
+                    creditPaytoUri = it.getString("credit_payto_uri"),
+                    wireTransferSubject = 
it.getString("wire_transfer_subject"),
+                    initiationTime = initiationTime,
+                    clientRequestUuid = it.getString("client_request_uuid")
+                )
+            } while (it.next())
+        }
+        return@runConn maybeMap
+    }
     /**
      * Initiate a payment in the database.  The "submit"
      * command is then responsible to pick it up and submit
-     * it at the bank.
+     * it to the bank.
      *
      * @param paymentData any data that's used to prepare the payment.
      * @return true if the insertion went through, false in case of errors.
      */
-    suspend fun initiatePayment(paymentData: InitiatedPayment): 
PaymentInitiationOutcome = runConn { conn ->
+    suspend fun initiatedPaymentCreate(paymentData: InitiatedPayment): 
PaymentInitiationOutcome = runConn { conn ->
         val stmt = conn.prepareStatement("""
            INSERT INTO initiated_outgoing_transactions (
              amount
              ,wire_transfer_subject
-             ,execution_time
              ,credit_payto_uri
+             ,initiation_time
              ,client_request_uuid
            ) VALUES (
              (?,?)::taler_amount
              ,?
              ,?
              ,?
-             ,?           
+             ,?
            )
         """)
         stmt.setLong(1, paymentData.amount.value)
         stmt.setInt(2, paymentData.amount.fraction)
         stmt.setString(3, paymentData.wireTransferSubject)
-        val executionTime = paymentData.executionTime.toDbMicros() ?: run {
-            logger.error("Execution time could not be converted to 
microseconds for the database.")
-            return@runConn PaymentInitiationOutcome.BAD_TIMESTAMP // nexus 
fault.
-        }
-        stmt.setLong(4, executionTime)
         val paytoOnlyIban = stripIbanPayto(paymentData.creditPaytoUri) ?: run {
             logger.error("Credit Payto address is invalid.")
             return@runConn PaymentInitiationOutcome.BAD_CREDIT_PAYTO // client 
fault.
         }
-        stmt.setString(5, paytoOnlyIban)
+        stmt.setString(4, paytoOnlyIban)
+        val initiationTime = paymentData.initiationTime.toDbMicros() ?: run {
+            throw Exception("Initiation time could not be converted to 
microseconds for the database.")
+        }
+        stmt.setLong(5, initiationTime)
         stmt.setString(6, paymentData.clientRequestUuid) // can be null.
         if (stmt.maybeUpdate())
             return@runConn PaymentInitiationOutcome.SUCCESS
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
new file mode 100644
index 00000000..4cc03f88
--- /dev/null
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
@@ -0,0 +1,43 @@
+package tech.libeufin.nexus
+
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import tech.libeufin.util.initializeDatabaseTables
+import tech.libeufin.util.resetDatabaseTables
+import kotlin.system.exitProcess
+
+fun doOrFail(doLambda: () -> Unit) {
+    try {
+        doLambda()
+    } catch (e: Exception) {
+        logger.error(e.message)
+        exitProcess(1)
+    }
+}
+
+/**
+ * This subcommand tries to load the SQL files that define
+ * the Nexus DB schema.  Admits the --reset option to delete
+ * the data first.
+ */
+class DbInit : CliktCommand("Initialize the libeufin-nexus database", name = 
"dbinit") {
+    private val configFile by option(
+        "--config", "-c",
+        help = "set the configuration file"
+    )
+    private val requestReset by option(
+        "--reset", "-r",
+        help = "reset database (DANGEROUS: All existing data is lost)"
+    ).flag()
+
+    override fun run() {
+        val cfg = loadConfigOrFail(configFile).extractDbConfigOrFail()
+        doOrFail {
+            if (requestReset) {
+                resetDatabaseTables(cfg, sqlFilePrefix = "libeufin-nexus")
+            }
+            initializeDatabaseTables(cfg, sqlFilePrefix = "libeufin-nexus")
+        }
+    }
+}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index 2125d0a0..b842b978 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -311,14 +311,8 @@ private fun findBic(maybeList: 
List<EbicsTypes.AccountInfo>?): String? {
  * @param configFile location of the configuration entry point.
  * @return internal representation of the configuration.
  */
-private fun extractConfig(configFile: String?): EbicsSetupConfig {
-    val config = TalerConfig(NEXUS_CONFIG_SOURCE)
-    try {
-        config.load(configFile)
-    } catch (e: Exception) {
-        logger.error("Could not load configuration from ${configFile}, detail: 
${e.message}")
-        exitProcess(1)
-    }
+private fun extractEbicsConfig(configFile: String?): EbicsSetupConfig {
+    val config = loadConfigOrFail(configFile)
     // Checking the config.
     val cfg = try {
         EbicsSetupConfig(config)
@@ -355,7 +349,7 @@ private fun makePdf(privs: ClientPrivateKeysFile, cfg: 
EbicsSetupConfig) {
 /**
  * CLI class implementing the "ebics-setup" subcommand.
  */
-class EbicsSetup: CliktCommand() {
+class EbicsSetup: CliktCommand("Set up the EBICS subscriber") {
     private val configFile by option(
         "--config", "-c",
         help = "set the configuration file"
@@ -380,7 +374,7 @@ class EbicsSetup: CliktCommand() {
      * This function collects the main steps of setting up an EBICS access.
      */
     override fun run() {
-        val cfg = extractConfig(this.configFile)
+        val cfg = extractEbicsConfig(this.configFile)
         if (checkFullConfig) {
             throw NotImplementedError("--check-full-config flag not 
implemented")
         }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index b148c4b2..8668912c 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -25,15 +25,11 @@
 package tech.libeufin.nexus
 import ConfigSource
 import TalerConfig
-import TalerConfigError
 import com.github.ajalt.clikt.core.CliktCommand
 import com.github.ajalt.clikt.core.subcommands
-import com.github.ajalt.clikt.parameters.options.flag
-import com.github.ajalt.clikt.parameters.options.option
 import com.github.ajalt.clikt.parameters.options.versionOption
 import io.ktor.client.*
 import io.ktor.util.*
-import kotlinx.coroutines.runBlocking
 import kotlinx.serialization.Contextual
 import kotlinx.serialization.KSerializer
 import org.slf4j.Logger
@@ -44,20 +40,15 @@ import kotlinx.serialization.Serializable
 import kotlinx.serialization.descriptors.PrimitiveKind
 import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
 import kotlinx.serialization.descriptors.SerialDescriptor
-import kotlinx.serialization.encodeToString
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.modules.SerializersModule
 import net.taler.wallet.crypto.Base32Crockford
-import org.slf4j.event.Level
 import tech.libeufin.nexus.ebics.*
 import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.EbicsTypes
 import java.security.interfaces.RSAPrivateCrtKey
 import java.security.interfaces.RSAPublicKey
-import java.time.Instant
-import kotlin.reflect.typeOf
 
 val NEXUS_CONFIG_SOURCE = ConfigSource("libeufin-nexus", "libeufin-nexus")
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.nexus.Main")
@@ -267,13 +258,44 @@ fun loadPrivateKeysFromDisk(location: String): 
ClientPrivateKeysFile? {
     }
 }
 
+/**
+ * Abstracts the config loading and exception handling.
+ *
+ * @param configFile potentially NULL configuration file location.
+ * @return the configuration handle.
+ */
+fun loadConfigOrFail(configFile: String?): TalerConfig {
+    val config = TalerConfig(NEXUS_CONFIG_SOURCE)
+    try {
+        config.load(configFile)
+    } catch (e: Exception) {
+        logger.error("Could not load configuration from ${configFile}, detail: 
${e.message}")
+        exitProcess(1)
+    }
+    return config
+}
+
+/**
+ * Abstracts fetching the DB config values to set up Nexus.
+ */
+fun TalerConfig.extractDbConfigOrFail(): DatabaseConfig =
+    try {
+        DatabaseConfig(
+            dbConnStr = requireString("nexus-postgres", "config"),
+            sqlDir = requirePath("libeufin-nexusdb-postgres", "sql_dir")
+        )
+    } catch (e: Exception) {
+        logger.error("Could not load config options for Nexus DB, detail: 
${e.message}.")
+        exitProcess(1)
+    }
+
 /**
  * Main CLI class that collects all the subcommands.
  */
 class LibeufinNexusCommand : CliktCommand() {
     init {
         versionOption(getVersion())
-        subcommands(EbicsSetup())
+        subcommands(EbicsSetup(), DbInit())
     }
     override fun run() = Unit
 }
diff --git a/nexus/src/test/kotlin/Common.kt b/nexus/src/test/kotlin/Common.kt
index 4845aaa8..3e89c854 100644
--- a/nexus/src/test/kotlin/Common.kt
+++ b/nexus/src/test/kotlin/Common.kt
@@ -8,6 +8,7 @@ import tech.libeufin.util.DatabaseConfig
 import tech.libeufin.util.initializeDatabaseTables
 import tech.libeufin.util.resetDatabaseTables
 import java.security.interfaces.RSAPrivateCrtKey
+import java.time.Instant
 
 val j = Json {
     this.serializersModule = SerializersModule {
@@ -54,6 +55,7 @@ fun getMockedClient(
     }
 }
 
+// Partial config to talk to PostFinance.
 fun getPofiConfig(userId: String, partnerId: String) = """
     [nexus-ebics]
     CURRENCY = KUDOS
@@ -67,4 +69,25 @@ fun getPofiConfig(userId: String, partnerId: String) = """
     CLIENT_PRIVATE_KEYS_FILE = /tmp/my-private-keys.json
     ACCOUNT_META_DATA_FILE = /tmp/ebics-meta.json
     BANK_DIALECT = postfinance
-""".trimIndent()
\ No newline at end of file
+""".trimIndent()
+
+// Generates a payment initiation, given its subject.
+fun genInitPay(subject: String, rowUuid: String? = null) =
+    InitiatedPayment(
+        amount = TalerAmount(44, 0, "KUDOS"),
+        creditPaytoUri = "payto://iban/not-used",
+        wireTransferSubject = subject,
+        initiationTime = Instant.now(),
+        clientRequestUuid = rowUuid
+    )
+
+// Generates an incoming payment, given its subject.
+fun genIncPay(subject: String, rowUuid: String? = null) =
+    IncomingPayment(
+        amount = TalerAmount(44, 0, "KUDOS"),
+        debitPaytoUri = "payto://iban/not-used",
+        wireTransferSubject = subject,
+        executionTime = Instant.now(),
+        bounced = false,
+        bankTransferId = "entropic"
+    )
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/DatabaseTest.kt 
b/nexus/src/test/kotlin/DatabaseTest.kt
index 2858af8e..d5301063 100644
--- a/nexus/src/test/kotlin/DatabaseTest.kt
+++ b/nexus/src/test/kotlin/DatabaseTest.kt
@@ -1,27 +1,128 @@
+import io.ktor.client.*
+import io.ktor.client.request.*
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import tech.libeufin.nexus.InitiatedPayment
 import tech.libeufin.nexus.NEXUS_CONFIG_SOURCE
 import tech.libeufin.nexus.PaymentInitiationOutcome
 import tech.libeufin.nexus.TalerAmount
+import tech.libeufin.util.transaction
 import java.time.Instant
 import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
 
-class DatabaseTest {
+class IncomingPaymentsTest {
+    // Tests the creation of an incoming payment.
+    @Test
+    fun incomingPaymentCreation() {
+        val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE))
+        val countRows = "SELECT count(*) AS how_many FROM 
incoming_transactions"
+        runBlocking {
+            // Asserting the table is empty.
+            db.runConn {
+                val res = it.execSQLQuery(countRows)
+                assertTrue(res.next())
+                assertEquals(0, res.getInt("how_many"))
+            }
+            db.incomingPaymentCreate(genIncPay("singleton"))
+            // Asserting the table has one.
+            db.runConn {
+                val res = it.execSQLQuery(countRows)
+                assertTrue(res.next())
+                assertEquals(1, res.getInt("how_many"))
+            }
+        }
+    }
+}
+class PaymentInitiationsTest {
+    // Tests the flagging of payments as submitted.
+    @Test
+    fun paymentInitiationSetAsSubmitted() {
+        val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE))
+        val getRowOne = """
+                    SELECT submitted
+                      FROM initiated_outgoing_transactions
+                      WHERE initiated_outgoing_transaction_id=1
+                """
+        runBlocking {
+            // Creating the record first.  Defaults to submitted == false.
+            assertEquals(
+                db.initiatedPaymentCreate(genInitPay("not submitted, has row 
ID == 1")),
+                PaymentInitiationOutcome.SUCCESS
+            )
+            // Asserting on the false default submitted state.
+            db.runConn { conn ->
+                val isSubmitted = conn.execSQLQuery(getRowOne)
+                assertTrue(isSubmitted.next())
+                assertFalse(isSubmitted.getBoolean("submitted"))
+            }
+            // Switching the submitted state to true.
+            assertTrue(db.initiatedPaymentSetSubmitted(1))
+            // Asserting on the submitted state being TRUE now.
+            db.runConn { conn ->
+                val isSubmitted = conn.execSQLQuery(getRowOne)
+                assertTrue(isSubmitted.next())
+                assertTrue(isSubmitted.getBoolean("submitted"))
+            }
+        }
+    }
 
+    // Tests creation, unique constraint violation handling, and
+    // retrieving only one non-submitted payment.
     @Test
     fun paymentInitiation() {
         val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE))
+        runBlocking {
+            val beEmpty = db.initiatedPaymentsUnsubmittedGet("KUDOS")// expect 
no records.
+            assertEquals(beEmpty.size, 0)
+        }
         val initPay = InitiatedPayment(
             amount = TalerAmount(44, 0, "KUDOS"),
             creditPaytoUri = "payto://iban/not-used",
-            executionTime = Instant.now(),
             wireTransferSubject = "test",
-            clientRequestUuid = "unique"
+            clientRequestUuid = "unique",
+            initiationTime = Instant.now()
         )
         runBlocking {
-            assertEquals(db.initiatePayment(initPay), 
PaymentInitiationOutcome.SUCCESS)
-            assertEquals(db.initiatePayment(initPay), 
PaymentInitiationOutcome.UNIQUE_CONSTRAINT_VIOLATION)
+            assertEquals(db.initiatedPaymentCreate(initPay), 
PaymentInitiationOutcome.SUCCESS)
+            assertEquals(db.initiatedPaymentCreate(initPay), 
PaymentInitiationOutcome.UNIQUE_CONSTRAINT_VIOLATION)
+            val haveOne = db.initiatedPaymentsUnsubmittedGet("KUDOS")
+            assertTrue {
+                haveOne.size == 1
+                        && haveOne.containsKey(1)
+                        && haveOne[1]?.clientRequestUuid == "unique"
+            }
+        }
+    }
+
+    // Tests how the fetch method gets the list of
+    // multiple unsubmitted payment initiations.
+    @Test
+    fun paymentInitiationsMultiple() {
+        val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE))
+        runBlocking {
+            assertEquals(db.initiatedPaymentCreate(genInitPay("#1")), 
PaymentInitiationOutcome.SUCCESS)
+            assertEquals(db.initiatedPaymentCreate(genInitPay("#2")), 
PaymentInitiationOutcome.SUCCESS)
+            assertEquals(db.initiatedPaymentCreate(genInitPay("#3")), 
PaymentInitiationOutcome.SUCCESS)
+            assertEquals(db.initiatedPaymentCreate(genInitPay("#4")), 
PaymentInitiationOutcome.SUCCESS)
+
+            // Marking one as submitted, hence not expecting it in the results.
+            db.runConn { conn ->
+                conn.execSQLUpdate("""
+                    UPDATE initiated_outgoing_transactions
+                      SET submitted = true
+                      WHERE initiated_outgoing_transaction_id=3;
+                """.trimIndent())
+            }
+
+            // Expecting all the payments BUT the #3 in the result.
+            db.initiatedPaymentsUnsubmittedGet("KUDOS").apply {
+                assertEquals(3, this.size)
+                assertEquals("#1", this[1]?.wireTransferSubject)
+                assertEquals("#2", this[2]?.wireTransferSubject)
+                assertEquals("#4", this[4]?.wireTransferSubject)
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/Keys.kt b/nexus/src/test/kotlin/Keys.kt
index db598d44..d2894c04 100644
--- a/nexus/src/test/kotlin/Keys.kt
+++ b/nexus/src/test/kotlin/Keys.kt
@@ -23,9 +23,9 @@ class PublicKeys {
             bank_encryption_public_key = 
CryptoUtil.generateRsaKeyPair(2028).public
         )
         // storing them on disk.
-        assertTrue(syncJsonToDisk(fileContent, config.bankPublicKeysFilename))
+        assertTrue(syncJsonToDisk(fileContent, 
"/tmp/nexus-tests-bank-keys.json"))
         // loading them and check that values are the same.
-        val fromDisk = loadBankKeys(config.bankPublicKeysFilename)
+        val fromDisk = loadBankKeys("/tmp/nexus-tests-bank-keys.json")
         assertNotNull(fromDisk)
         assertTrue {
             fromDisk.accepted &&
diff --git a/util/src/main/kotlin/DB.kt b/util/src/main/kotlin/DB.kt
index 9c530e2b..3e0b55fe 100644
--- a/util/src/main/kotlin/DB.kt
+++ b/util/src/main/kotlin/DB.kt
@@ -345,6 +345,7 @@ fun pgDataSource(dbConfig: String): PGSimpleDataSource {
 
 fun PGSimpleDataSource.pgConnection(): PgConnection {
     val conn = connection.unwrap(PgConnection::class.java)
+    // FIXME: bring the DB schema to a function argument.
     conn.execSQLUpdate("SET search_path TO libeufin_bank;")
     return conn
 }
@@ -400,8 +401,12 @@ fun initializeDatabaseTables(cfg: DatabaseConfig, 
sqlFilePrefix: String) {
                 val sqlPatchText = path.readText()
                 conn.execSQLUpdate(sqlPatchText)
             }
-            val sqlProcedures = File("${cfg.sqlDir}/procedures.sql").readText()
-            conn.execSQLUpdate(sqlProcedures)
+            val sqlProcedures = File("${cfg.sqlDir}/procedures.sql")
+            if (!sqlProcedures.exists()) {
+                logger.info("No procedures.sql for the SQL collection: 
$sqlFilePrefix")
+                return@transaction
+            }
+            conn.execSQLUpdate(sqlProcedures.readText())
         }
     }
 }

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]