2023-03-31 17:27:05 +02:00
|
|
|
/*
|
|
|
|
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";
|
2023-03-31 17:27:05 +02:00
|
|
|
|
|
|
|
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");
|
2023-03-31 17:27:05 +02:00
|
|
|
});
|