[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: Bank DB refactoring.
From: |
gnunet |
Subject: |
[libeufin] branch master updated: Bank DB refactoring. |
Date: |
Fri, 01 Sep 2023 10:42:56 +0200 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository libeufin.
The following commit(s) were added to refs/heads/master by this push:
new 7a68b2f0 Bank DB refactoring.
7a68b2f0 is described below
commit 7a68b2f0a0603b2bcb34110019254d4d85d91321
Author: MS <ms@taler.net>
AuthorDate: Fri Sep 1 10:42:07 2023 +0200
Bank DB refactoring.
Fixing off-by-one when bringing one account
from debit to the credit state.
---
database-versioning/new/procedures.sql | 36 +++---
.../main/kotlin/tech/libeufin/sandbox/Database.kt | 7 +-
sandbox/src/test/kotlin/DatabaseTest.kt | 135 ++++++++++++++++-----
3 files changed, 131 insertions(+), 47 deletions(-)
diff --git a/database-versioning/new/procedures.sql
b/database-versioning/new/procedures.sql
index 02a0e400..17fbde32 100644
--- a/database-versioning/new/procedures.sql
+++ b/database-versioning/new/procedures.sql
@@ -116,10 +116,11 @@ creditor_balance taler_amount;
potential_balance taler_amount;
potential_balance_check BOOLEAN;
new_debtor_balance taler_amount;
+new_debtor_balance_ok BOOLEAN;
new_creditor_balance taler_amount;
will_debtor_have_debt BOOLEAN;
will_creditor_have_debt BOOLEAN;
-spending_capacity taler_amount;
+amount_at_least_debit BOOLEAN;
potential_balance_ok BOOLEAN;
BEGIN
-- check debtor exists.
@@ -165,7 +166,9 @@ out_nx_creditor=FALSE;
-- check debtor has enough funds.
IF (debtor_has_debt)
THEN -- debt case: simply checking against the max debt allowed.
- CALL amount_add(debtor_balance, in_amount, potential_balance);
+ CALL amount_add(debtor_balance,
+ in_amount,
+ potential_balance);
SELECT ok
INTO potential_balance_check
FROM amount_left_minus_right(debtor_max_debt,
@@ -221,24 +224,27 @@ THEN
will_creditor_have_debt=FALSE;
ELSE -- creditor had debit but MIGHT switch to credit.
SELECT
- (diff).val, (diff).frac
- INTO new_creditor_balance.val, new_creditor_balance.frac
- FROM amount_left_minus_right(creditor_balance,
- in_amount);
- IF (new_debtor_balance.ok)
- -- the debt is bigger than the amount, keep
- -- this last calculated balance but stay debt.
+ (diff).val, (diff).frac,
+ ok
+ INTO
+ new_creditor_balance.val, new_creditor_balance.frac,
+ amount_at_least_debit
+ FROM amount_left_minus_right(in_amount,
+ creditor_balance);
+ IF (amount_at_least_debit)
+ -- the amount is at least as big as the debit, can switch to credit then.
THEN
- will_creditor_have_debt=TRUE;
+ will_creditor_have_debt=FALSE;
+ -- compute new balance.
ELSE
- -- the amount would bring the account back to credit,
- -- determine by how much.
+ -- the amount is not enough to bring the receiver
+ -- to a credit state, switch operators to calculate the new balance.
SELECT
(diff).val, (diff).frac
INTO new_creditor_balance.val, new_creditor_balance.frac
- FROM amount_left_minus_right(in_amount,
- creditor_balance);
- will_creditor_have_debt=FALSE;
+ FROM amount_left_minus_right(creditor_balance,
+ in_amount);
+ will_creditor_have_debt=TRUE;
END IF;
END IF;
out_balance_insufficient=FALSE;
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
index e7ca959a..90dd39dc 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
@@ -33,7 +33,8 @@ data class BankAccount(
val owningCustomerId: Long,
val isPublic: Boolean = false,
val lastNexusFetchRowId: Long,
- val balance: TalerAmount? = null
+ val balance: TalerAmount? = null,
+ val hasDebt: Boolean
)
enum class TransactionDirection {
@@ -254,6 +255,7 @@ class Database(private val dbConfig: String) {
,last_nexus_fetch_row_id
,(balance).val AS balance_value
,(balance).frac AS balance_frac
+ ,has_debt
FROM bank_accounts
WHERE bank_account_label=?
""")
@@ -271,7 +273,8 @@ class Database(private val dbConfig: String) {
),
bankAccountLabel = bankAccountLabel,
lastNexusFetchRowId = it.getLong("last_nexus_fetch_row_id"),
- owningCustomerId = it.getLong("owning_customer_id")
+ owningCustomerId = it.getLong("owning_customer_id"),
+ hasDebt = it.getBoolean("has_debt")
)
}
}
diff --git a/sandbox/src/test/kotlin/DatabaseTest.kt
b/sandbox/src/test/kotlin/DatabaseTest.kt
index 7fa4b3f7..eb7ef4d8 100644
--- a/sandbox/src/test/kotlin/DatabaseTest.kt
+++ b/sandbox/src/test/kotlin/DatabaseTest.kt
@@ -3,7 +3,7 @@ import tech.libeufin.sandbox.*
import tech.libeufin.util.execCommand
class DatabaseTest {
- private val c = Customer(
+ private val customerFoo = Customer(
login = "foo",
passwordHash = "hash",
name = "Foo",
@@ -12,7 +12,7 @@ class DatabaseTest {
cashoutPayto = "payto://external-IBAN",
cashoutCurrency = "KUDOS"
)
- private val c1 = Customer(
+ private val customerBar = Customer(
login = "bar",
passwordHash = "hash",
name = "Bar",
@@ -21,6 +21,23 @@ class DatabaseTest {
cashoutPayto = "payto://external-IBAN",
cashoutCurrency = "KUDOS"
)
+ private val bankAccountFoo = BankAccount(
+ iban = "FOO-IBAN-XYZ",
+ bic = "FOO-BIC",
+ bankAccountLabel = "foo",
+ lastNexusFetchRowId = 1L,
+ owningCustomerId = 1L,
+ hasDebt = false
+ )
+ private val bankAccountBar = BankAccount(
+ iban = "BAR-IBAN-ABC",
+ bic = "BAR-BIC",
+ bankAccountLabel = "bar",
+ lastNexusFetchRowId = 1L,
+ owningCustomerId = 2L,
+ hasDebt = false
+ )
+
fun initDb(): Database {
execCommand(
listOf(
@@ -31,50 +48,107 @@ class DatabaseTest {
),
throwIfFails = true
)
- return Database("jdbc:postgresql:///libeufincheck")
+ val db = Database("jdbc:postgresql:///libeufincheck")
+ // Need accounts first.
+ db.customerCreate(customerFoo)
+ db.customerCreate(customerBar)
+ db.bankAccountCreate(bankAccountFoo)
+ db.bankAccountCreate(bankAccountBar)
+ db.bankAccountSetMaxDebt(
+ "foo",
+ TalerAmount(100, 0)
+ )
+ db.bankAccountSetMaxDebt(
+ "bar",
+ TalerAmount(50, 0)
+ )
+ return db
}
@Test
- fun bankTransactionTest() {
+ fun bankTransactionsTest() {
val db = initDb()
- // Need accounts first.
- db.customerCreate(c)
- db.customerCreate(c1)
- db.bankAccountCreate(BankAccount(
- iban = "FOO-IBAN-XYZ",
- bic = "FOO-BIC",
- bankAccountLabel = "foo",
- lastNexusFetchRowId = 1L,
- owningCustomerId = 1L
- ))
- db.bankAccountCreate(BankAccount(
- iban = "BAR-IBAN-ABC",
- bic = "BAR-BIC",
- bankAccountLabel = "bar",
- lastNexusFetchRowId = 1L,
- owningCustomerId = 2L
- ))
- db.bankAccountSetMaxDebt("foo", TalerAmount(100, 0))
- val res = db.bankTransactionCreate(BankInternalTransaction(
+ var fooAccount = db.bankAccountGetFromLabel("foo")
+ assert(fooAccount?.hasDebt == false) // Foo has NO debit.
+ // Preparing the payment data.
+ val fooPaysBar = BankInternalTransaction(
creditorAccountId = 2,
debtorAccountId = 1,
subject = "test",
- amount = TalerAmount(3, 333),
+ amount = TalerAmount(10, 0),
accountServicerReference = "acct-svcr-ref",
endToEndId = "end-to-end-id",
paymentInformationId = "pmtinfid",
transactionDate = 100000L
- ))
- assert(res == Database.BankTransactionResult.SUCCESS)
+ )
+ val firstSpending = db.bankTransactionCreate(fooPaysBar) // Foo pays
Bar and goes debit.
+ assert(firstSpending == Database.BankTransactionResult.SUCCESS)
+ fooAccount = db.bankAccountGetFromLabel("foo")
+ // Foo: credit -> debit
+ assert(fooAccount?.hasDebt == true) // Asserting Foo's debit.
+ // Now checking that more spending doesn't get Foo out of debit.
+ val secondSpending = db.bankTransactionCreate(fooPaysBar)
+ assert(secondSpending == Database.BankTransactionResult.SUCCESS)
+ fooAccount = db.bankAccountGetFromLabel("foo")
+ // Checking that Foo's debit is two times the paid amount
+ // Foo: debit -> debit
+ assert(fooAccount?.balance?.value == 20L
+ && fooAccount.balance?.frac == 0
+ && fooAccount.hasDebt
+ )
+ // Asserting Bar has a positive balance and what Foo paid so far.
+ var barAccount = db.bankAccountGetFromLabel("bar")
+ val barBalance: TalerAmount? = barAccount?.balance
+ assert(
+ barAccount?.hasDebt == false
+ && barBalance?.value == 20L && barBalance.frac == 0
+ )
+ // Bar pays so that its balance remains positive.
+ val barPaysFoo = BankInternalTransaction(
+ creditorAccountId = 1,
+ debtorAccountId = 2,
+ subject = "test",
+ amount = TalerAmount(10, 0),
+ accountServicerReference = "acct-svcr-ref",
+ endToEndId = "end-to-end-id",
+ paymentInformationId = "pmtinfid",
+ transactionDate = 100000L
+ )
+ val barPays = db.bankTransactionCreate(barPaysFoo)
+ assert(barPays == Database.BankTransactionResult.SUCCESS)
+ barAccount = db.bankAccountGetFromLabel("bar")
+ val barBalanceTen: TalerAmount? = barAccount?.balance
+ // Bar: credit -> credit
+ assert(barAccount?.hasDebt == false && barBalanceTen?.value == 10L &&
barBalanceTen.frac == 0)
+ // Bar pays again to let Foo return in credit.
+ val barPaysAgain = db.bankTransactionCreate(barPaysFoo)
+ assert(barPaysAgain == Database.BankTransactionResult.SUCCESS)
+ // Refreshing the two accounts.
+ barAccount = db.bankAccountGetFromLabel("bar")
+ fooAccount = db.bankAccountGetFromLabel("foo")
+ // Foo should have returned to zero and no debt, same for Bar.
+ // Foo: debit -> credit
+ assert(fooAccount?.hasDebt == false && barAccount?.hasDebt == false)
+ assert(fooAccount?.balance?.equals(TalerAmount(0, 0)) == true)
+ assert(barAccount?.balance?.equals(TalerAmount(0, 0)) == true)
+ // Bringing Bar to debit.
+ val barPaysMore = db.bankTransactionCreate(barPaysFoo)
+ assert(barPaysAgain == Database.BankTransactionResult.SUCCESS)
+ barAccount = db.bankAccountGetFromLabel("bar")
+ fooAccount = db.bankAccountGetFromLabel("foo")
+ // Bar: credit -> debit
+ assert(fooAccount?.hasDebt == false && barAccount?.hasDebt == true)
+ assert(fooAccount?.balance?.equals(TalerAmount(10, 0)) == true)
+ assert(barAccount?.balance?.equals(TalerAmount(10, 0)) == true)
}
@Test
fun customerCreationTest() {
val db = initDb()
assert(db.customerGetFromLogin("foo") == null)
- db.customerCreate(c)
+ db.customerCreate(customerFoo)
assert(db.customerGetFromLogin("foo")?.name == "Foo")
// Trigger conflict.
- assert(!db.customerCreate(c))
+ assert(!db.customerCreate(customerFoo))
}
@Test
fun configTest() {
@@ -93,9 +167,10 @@ class DatabaseTest {
bic = "not used",
bankAccountLabel = "foo",
lastNexusFetchRowId = 1L,
- owningCustomerId = 1L
+ owningCustomerId = 1L,
+ hasDebt = false
)
- db.customerCreate(c) // Satisfies the REFERENCE
+ db.customerCreate(customerFoo) // Satisfies the REFERENCE
assert(db.bankAccountCreate(bankAccount))
assert(!db.bankAccountCreate(bankAccount)) // Triggers conflict.
assert(db.bankAccountGetFromLabel("foo")?.bankAccountLabel == "foo")
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.