Skip to content
Snippets Groups Projects
Commit 3300af22 authored by Moul's avatar Moul
Browse files

[feat] #134: Add ability to pass a file containing the tx recipients and amounts

Introduce function to parse the file and compute the values

Comments (#) are ignored
ABSOLUTE/RELATIVE header required to specify the reference
of the specified amounts

Set recipients argument to no longer required, add a check instead
Set file mutuality exclusive with amounts(UD), allsources, and recipients
Update tx cli tests

Add tests
parent 3805f52b
No related branches found
No related tags found
1 merge request!202#134: Add ability to pass a file containing the tx recipients and amounts
Pipeline #15116 failed
......@@ -13,8 +13,12 @@
# You should have received a copy of the GNU Affero General Public License
# along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
import math
import shlex
import sys
from re import compile, search
from typing import List
import click
from duniterpy.api.bma.tx import process
......@@ -60,7 +64,7 @@ NBR_ISSUERS = 1
type=click.FloatRange(MINIMAL_ABSOLUTE_TX_AMOUNT),
help=f"Quantitative amount(s):\n-a <amount>\nMinimum amount is {MINIMAL_ABSOLUTE_TX_AMOUNT}.",
cls=cli_tools.MutuallyExclusiveOption,
mutually_exclusive=["amountsud", "allsources"],
mutually_exclusive=["amountsud", "allsources", "file_path"],
)
@click.option(
"amountsud",
......@@ -70,25 +74,34 @@ NBR_ISSUERS = 1
type=click.FloatRange(MINIMAL_RELATIVE_TX_AMOUNT),
help=f"Relative amount(s):\n-d <amount_UD>\nMinimum amount is {MINIMAL_RELATIVE_TX_AMOUNT}",
cls=cli_tools.MutuallyExclusiveOption,
mutually_exclusive=["amounts", "allsources"],
mutually_exclusive=["amounts", "allsources", "file_path"],
)
@click.option(
"--allSources",
is_flag=True,
help="Send all sources to one recipient",
cls=cli_tools.MutuallyExclusiveOption,
mutually_exclusive=["amounts", "amountsud"],
mutually_exclusive=["amounts", "amountsud", "file_path"],
)
@click.option(
"recipients",
"--recipient",
"-r",
multiple=True,
required=True,
help="Pubkey(s)’ recipients + optional checksum:\n-r <pubkey>[:checksum]\n\
Sending to many recipients is possible:\n\
* With one amount, all will receive the amount\n\
* With many amounts (one per recipient)",
cls=cli_tools.MutuallyExclusiveOption,
mutually_exclusive=["file_path"],
)
@click.option(
"file_path",
"--file",
"-f",
help="File’s path containing a list of amounts in absolute or relative reference and recipients’ pubkeys",
cls=cli_tools.MutuallyExclusiveOption,
mutually_exclusive=["recipients", "amounts", "amountsUD", "allsources"],
)
@click.option("--comment", "-c", default="", help="Comment")
@click.option(
......@@ -103,15 +116,18 @@ def send_transaction(
amountsud,
allsources,
recipients,
file_path,
comment,
outputbackchange,
yes,
):
"""
Main function
"""
if file_path:
tx_amounts, recipients = parse_file_containing_amounts_recipients(file_path)
else:
if not (amounts or amountsud or allsources):
tools.message_exit("Error: amount, amountUD or allSources is not set.")
if not recipients:
tools.message_exit("Error: A recipient should be passed")
if allsources and len(recipients) > 1:
tools.message_exit(
"Error: the --allSources option can only be used with one recipient."
......@@ -167,6 +183,53 @@ def send_transaction(
)
def parse_file_containing_amounts_recipients(file_path: str) -> List:
"""
Parse file in a specific format
Comments are ignored
Format should be:
```txt
[ABSOLUTE/RELATIVE]
# comment1
amount1 recipient1’s pubkey
# comment2
amount2 recipient2’s pubkey
```
"""
reference = ""
amounts, recipients = [], []
with open(file_path) as file:
for n, line in enumerate(file):
line = shlex.split(line, True)
if line:
if n == 0:
reference = line[0]
else:
try:
amounts.append(float(line[0]))
recipients.append(line[1])
except (ValueError, IndexError):
tools.message_exit(f"Syntax error at line {n + 1}")
if not reference or (reference != "ABSOLUTE" and reference != "RELATIVE"):
tools.message_exit(
f"{file_path} must contain at first line 'ABSOLUTE' or 'RELATIVE' header"
)
if not amounts or not recipients:
tools.message_exit("No amounts or recipients specified")
# Compute amount depending on the reference
if reference == "ABSOLUTE":
reference_mult = CENT_MULT_TO_UNIT
else:
reference_mult = money.UDValue().ud_value
tx_amounts = compute_amounts(amounts, reference_mult)
return tx_amounts, recipients
def transaction_amount(amounts, UDs_amounts, outputAddresses):
"""
Check that the number of passed amounts(UD) and recipients are the same
......
......@@ -135,16 +135,16 @@ def test_transaction_amount_errors(
def test_tx_passed_amount_cli():
"""One option"""
result = CliRunner().invoke(cli, ["tx", "--amount", "1"])
assert "Error: Missing option" in result.output
assert result.exit_code == 2
assert "Error: A recipient should be passed\n" in result.output
assert result.exit_code == 1
result = CliRunner().invoke(cli, ["tx", "--amountUD", "1"])
assert "Error: Missing option" in result.output
assert result.exit_code == 2
assert "Error: A recipient should be passed\n" in result.output
assert result.exit_code == 1
result = CliRunner().invoke(cli, ["tx", "--allSources"])
assert "Error: Missing option" in result.output
assert result.exit_code == 2
assert "Error: A recipient should be passed\n" in result.output
assert result.exit_code == 1
"""Multiple options"""
result = CliRunner().invoke(cli, ["tx", "--amount", 1, "--amountUD", 1])
......
# Copyright 2016-2022 Maël Azimi <m.a@moul.re>
#
# Silkaj is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Silkaj 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
import pytest
from click.testing import CliRunner
from silkaj.constants import CENT_MULT_TO_UNIT
from silkaj.money import UDValue
from silkaj.tx import parse_file_containing_amounts_recipients
FILE_PATH = "recipients.txt"
ud_value = UDValue().ud_value
@pytest.mark.parametrize(
"file_content, amounts_exp, recipients_exp",
[
(
"ABSOLUTE\n10 pubkey1\n20 pubkey2",
[10 * CENT_MULT_TO_UNIT, 20 * CENT_MULT_TO_UNIT],
["pubkey1", "pubkey2"],
),
(
"RELATIVE\n#toto\n10 pubkey1\n#titi\n20 pubkey2",
[10 * ud_value, 20 * ud_value],
["pubkey1", "pubkey2"],
),
],
)
def test_parse_file_containing_amounts_recipients(
file_content, amounts_exp, recipients_exp
):
runner = CliRunner()
with runner.isolated_filesystem():
with open(FILE_PATH, "w") as f:
f.write(file_content)
amounts, recipients = parse_file_containing_amounts_recipients(FILE_PATH)
assert amounts == amounts_exp
assert recipients == recipients_exp
HEADER_ERR = (
"recipients.txt must contain at first line 'ABSOLUTE' or 'RELATIVE' header\n"
)
SYNTAX_ERR = "Syntax error at line"
SPEC_ERR = "No amounts or recipients specified"
@pytest.mark.parametrize(
"file_content, error",
[
("ABSOLUTE\n10 pubkey1\n20", SYNTAX_ERR),
("#RELATIVE\n10 pubkey1\n20 pubkey2", HEADER_ERR),
("RELATIVE\npubkey1 10\n20 pubkey2", SYNTAX_ERR),
("ABSOLUTE", SPEC_ERR),
("", HEADER_ERR),
],
)
def test_parse_file_containing_amounts_recipients_errors(file_content, error, capsys):
runner = CliRunner()
with runner.isolated_filesystem():
with open(FILE_PATH, "w") as f:
f.write(file_content)
with pytest.raises(SystemExit) as pytest_exit:
parse_file_containing_amounts_recipients(FILE_PATH)
assert error in capsys.readouterr().out
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment