/* 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 { calculatePlanFormAvailableCoins, selectCoinForOperation, } from "./coinSelection.js"; import { AbsoluteTime, AgeRestriction, AmountJson, Amounts, Duration, 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("credit", kudos(2), "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("credit", kudos(4), "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("send effective 6", (t) => { const coinList: Coin[] = [ [kudos(2), 5], [kudos(5), 5], ]; const result = selectCoinForOperation("debit", kudos(6), "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("debit", kudos(6), "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("debit", kudos(20), "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), "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), "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), "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"); });