/* This file is part of GNU Taler (C) 2022 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ import test, { ExecutionContext } from "ava"; import { AmountMode, OperationType, calculatePlanFormAvailableCoins, selectCoinForOperation, } from "./coinSelection.js"; import { AbsoluteTime, AgeRestriction, AmountJson, Amounts, Duration, TransactionAmountMode, TransactionType, } from "@gnu-taler/taler-util"; function expect(t: ExecutionContext, thing: any): any { return { deep: { equal: (another: any) => t.deepEqual(thing, another), equals: (another: any) => t.deepEqual(thing, another), }, }; } function kudos(v: number): AmountJson { return Amounts.fromFloat(v, "KUDOS"); } function defaultFeeConfig(value: AmountJson, totalAvailable: number) { return { id: Amounts.stringify(value), denomDeposit: kudos(0.01), denomRefresh: kudos(0.01), denomWithdraw: kudos(0.01), duration: Duration.getForever(), exchangePurse: undefined, exchangeWire: undefined, maxAge: AgeRestriction.AGE_UNRESTRICTED, totalAvailable, value, }; } type Coin = [AmountJson, number]; /** * selectCoinForOperation test * * Test here should check that the correct coins are selected */ test("get effective 2", (t) => { const coinList: Coin[] = [ [kudos(2), 5], [kudos(5), 5], ]; const result = selectCoinForOperation( OperationType.Credit, kudos(2), AmountMode.Net, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: {}, }, ); expect(t, result.coins).deep.equal(["KUDOS:2"]); t.assert(result.refresh === undefined); }); test("get raw 4", (t) => { const coinList: Coin[] = [ [kudos(2), 5], [kudos(5), 5], ]; const result = selectCoinForOperation( OperationType.Credit, kudos(4), AmountMode.Gross, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: {}, }, ); expect(t, result.coins).deep.equal(["KUDOS:2", "KUDOS:2"]); t.assert(result.refresh === undefined); }); test("get raw 25, diff with demo ", (t) => { const coinList: Coin[] = [ [kudos(0.1), 0], [kudos(1), 0], [kudos(2), 0], [kudos(5), 0], [kudos(10), 0], ]; const result = selectCoinForOperation( OperationType.Credit, kudos(25), AmountMode.Gross, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: {}, }, ); expect(t, result.coins).deep.equal(["KUDOS:10", "KUDOS:10", "KUDOS:5"]); t.assert(result.refresh === undefined); }); test("send effective 6", (t) => { const coinList: Coin[] = [ [kudos(2), 5], [kudos(5), 5], ]; const result = selectCoinForOperation( OperationType.Debit, kudos(6), AmountMode.Gross, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: {}, }, ); expect(t, result.coins).deep.equal(["KUDOS:5"]); t.assert(result.refresh?.selected === "KUDOS:2"); }); test("send raw 6", (t) => { const coinList: Coin[] = [ [kudos(2), 5], [kudos(5), 5], ]; const result = selectCoinForOperation( OperationType.Debit, kudos(6), AmountMode.Gross, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: {}, }, ); expect(t, result.coins).deep.equal(["KUDOS:5"]); t.assert(result.refresh?.selected === "KUDOS:2"); }); test("send raw 20 (not enough)", (t) => { const coinList: Coin[] = [ [kudos(2), 1], [kudos(5), 2], ]; const result = selectCoinForOperation( OperationType.Debit, kudos(20), AmountMode.Gross, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: {}, }, ); expect(t, result.coins).deep.equal(["KUDOS:5", "KUDOS:5", "KUDOS:2"]); t.assert(result.refresh === undefined); }); /** * calculatePlanFormAvailableCoins test * * Test here should check that the plan summary for a transaction is correct * * effective amount * * raw amount */ test("deposit effective 2 ", (t) => { const coinList: Coin[] = [ [kudos(2), 1], [kudos(5), 2], ]; const result = calculatePlanFormAvailableCoins( TransactionType.Deposit, kudos(2), TransactionAmountMode.Effective, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: { "2": { creditDeadline: AbsoluteTime.never(), debitDeadline: AbsoluteTime.never(), wireFee: kudos(0.01), purseFee: kudos(0.01), }, }, }, ); t.deepEqual(result.rawAmount, "KUDOS:1.98"); t.deepEqual(result.effectiveAmount, "KUDOS:2"); }); test("deposit raw 2 ", (t) => { const coinList: Coin[] = [ [kudos(2), 1], [kudos(5), 2], ]; const result = calculatePlanFormAvailableCoins( TransactionType.Deposit, kudos(2), TransactionAmountMode.Raw, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: { "2": { creditDeadline: AbsoluteTime.never(), debitDeadline: AbsoluteTime.never(), wireFee: kudos(0.01), purseFee: kudos(0.01), }, }, }, ); t.deepEqual(result.rawAmount, "KUDOS:2"); t.deepEqual(result.effectiveAmount, "KUDOS:2.04"); }); test("withdraw raw 21 ", (t) => { const coinList: Coin[] = [ [kudos(2), 1], [kudos(5), 2], ]; const result = calculatePlanFormAvailableCoins( TransactionType.Withdrawal, kudos(21), TransactionAmountMode.Raw, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: { "2": { creditDeadline: AbsoluteTime.never(), debitDeadline: AbsoluteTime.never(), wireFee: kudos(0.01), purseFee: kudos(0.01), }, }, }, ); // denominations configuration is not suitable // for greedy algorithm t.deepEqual(result.rawAmount, "KUDOS:20"); t.deepEqual(result.effectiveAmount, "KUDOS:19.96"); }); test("withdraw raw 25, diff with demo ", (t) => { const coinList: Coin[] = [ [kudos(0.1), 0], [kudos(1), 0], [kudos(2), 0], [kudos(5), 0], [kudos(10), 0], ]; const result = calculatePlanFormAvailableCoins( TransactionType.Withdrawal, kudos(25), TransactionAmountMode.Raw, { list: coinList.map(([v, t]) => defaultFeeConfig(v, t)), exchanges: { "2": { creditDeadline: AbsoluteTime.never(), debitDeadline: AbsoluteTime.never(), wireFee: kudos(0.01), purseFee: kudos(0.01), }, }, }, ); t.deepEqual(result.rawAmount, "KUDOS:25"); // here demo report KUDOS:0.2 fee // t.deepEqual(result.effectiveAmount, "KUDOS:24.80"); t.deepEqual(result.effectiveAmount, "KUDOS:24.97"); });