wallet-core/packages/taler-wallet-core/src/util/coinSelection.test.ts

249 lines
5.9 KiB
TypeScript
Raw Normal View History

/*
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 <http://www.gnu.org/licenses/>
*/
import test, { ExecutionContext } from "ava";
2023-06-15 18:07:31 +02:00
import {
2023-06-16 14:40:45 +02:00
AmountMode,
OperationType,
2023-06-15 18:07:31 +02:00
calculatePlanFormAvailableCoins,
selectCoinForOperation,
} from "./coinSelection.js";
import {
AbsoluteTime,
AgeRestriction,
AmountJson,
Amounts,
Duration,
2023-06-16 14:40:45 +02:00
TransactionAmountMode,
2023-06-15 18:07:31 +02:00
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),
},
};
}
2023-06-15 18:07:31 +02:00
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],
];
2023-06-16 14:40:45 +02:00
const result = selectCoinForOperation(
OperationType.Credit,
kudos(2),
AmountMode.Net,
{
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
exchanges: {},
},
);
2023-06-15 18:07:31 +02:00
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],
];
2023-06-16 14:40:45 +02:00
const result = selectCoinForOperation(
OperationType.Credit,
kudos(4),
AmountMode.Gross,
{
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
exchanges: {},
},
);
2023-06-15 18:07:31 +02:00
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],
];
2023-06-16 14:40:45 +02:00
const result = selectCoinForOperation(
OperationType.Debit,
kudos(6),
AmountMode.Gross,
{
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
exchanges: {},
},
);
2023-06-15 18:07:31 +02:00
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],
];
2023-06-16 14:40:45 +02:00
const result = selectCoinForOperation(
OperationType.Debit,
kudos(6),
AmountMode.Gross,
{
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
exchanges: {},
},
);
2023-06-15 18:07:31 +02:00
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],
];
2023-06-16 14:40:45 +02:00
const result = selectCoinForOperation(
OperationType.Debit,
kudos(20),
AmountMode.Gross,
{
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
exchanges: {},
},
);
2023-06-15 18:07:31 +02:00
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),
2023-06-16 14:40:45 +02:00
TransactionAmountMode.Effective,
2023-06-15 18:07:31 +02:00
{
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),
2023-06-16 14:40:45 +02:00
TransactionAmountMode.Raw,
2023-06-15 18:07:31 +02:00
{
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),
2023-06-16 14:40:45 +02:00
TransactionAmountMode.Raw,
2023-06-15 18:07:31 +02:00
{
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");
});