From 0ddaf5893bd27205554815760ab761dc5b32fec4 Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Tue, 3 Aug 2021 14:06:41 +0200 Subject: [PATCH] Basic tests for cli(). --- src/pricehist/cli.py | 16 ++-- tests/pricehist/test_cli.py | 168 ++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 tests/pricehist/test_cli.py diff --git a/src/pricehist/cli.py b/src/pricehist/cli.py index 669a980..d676856 100644 --- a/src/pricehist/cli.py +++ b/src/pricehist/cli.py @@ -10,7 +10,7 @@ from pricehist.format import Format from pricehist.series import Series -def cli(argv=sys.argv, output_file=sys.stdout): +def cli(argv=sys.argv): start_time = datetime.now() logger.init() @@ -25,20 +25,20 @@ def cli(argv=sys.argv, output_file=sys.stdout): try: if args.version: - print(f"pricehist {__version__}", file=output_file) + print(f"pricehist {__version__}") elif args.command == "sources": result = sources.formatted() - print(result, file=output_file) + print(result) elif args.command == "source" and args.symbols: result = sources.by_id[args.source].format_symbols() - print(result, file=output_file, end="") + print(result, end="") elif args.command == "source" and args.search: result = sources.by_id[args.source].format_search(args.search) - print(result, file=output_file, end="") + print(result, end="") elif args.command == "source": total_width = shutil.get_terminal_size().columns result = sources.by_id[args.source].format_info(total_width) - print(result, file=output_file) + print(result) elif args.command == "fetch": source = sources.by_id[args.source] output = outputs.by_type[args.output] @@ -60,9 +60,9 @@ def cli(argv=sys.argv, output_file=sys.stdout): ) fmt = Format.fromargs(args) result = fetch(series, source, output, args.invert, args.quantize, fmt) - print(result, end="", file=output_file) + print(result, end="") else: - parser.print_help(file=sys.stderr) + parser.print_help() except BrokenPipeError: logging.debug("The output pipe was closed early.") finally: diff --git a/tests/pricehist/test_cli.py b/tests/pricehist/test_cli.py new file mode 100644 index 0000000..cd78e46 --- /dev/null +++ b/tests/pricehist/test_cli.py @@ -0,0 +1,168 @@ +import argparse +import logging +from decimal import Decimal + +import pytest + +from pricehist import __version__, cli, sources + + +def w(string): + return string.split(" ") + + +def test_valid_pair(): + assert cli.valid_pair("BTC/AUD") == ("BTC", "AUD") + assert cli.valid_pair("BTC/AUD/ignored") == ("BTC", "AUD") + assert cli.valid_pair("SYM") == ("SYM", "") + assert cli.valid_pair("SYM/") == ("SYM", "") + with pytest.raises(argparse.ArgumentTypeError): + cli.valid_pair("/SYM") + with pytest.raises(argparse.ArgumentTypeError): + cli.valid_pair("") + + +def test_valid_date(): + assert cli.valid_date("today") == cli.today() + assert cli.valid_date("2021-12-30") == "2021-12-30" + with pytest.raises(argparse.ArgumentTypeError) as e: + cli.valid_date("2021-12-40") + assert "Not a valid" in str(e.value) + + +def test_valid_date_before(): + assert cli.valid_date_before("2021-12-30") == "2021-12-29" + with pytest.raises(argparse.ArgumentTypeError) as e: + cli.valid_date_before("2021-12-40") + assert "Not a valid" in str(e.value) + + +def test_valid_date_after(): + assert cli.valid_date_after("2021-12-30") == "2021-12-31" + with pytest.raises(argparse.ArgumentTypeError) as e: + cli.valid_date_after("2021-12-40") + assert "Not a valid" in str(e.value) + + +def test_valid_char(): + assert cli.valid_char(",") == "," + with pytest.raises(argparse.ArgumentTypeError): + cli.valid_char("") + with pytest.raises(argparse.ArgumentTypeError): + cli.valid_char("12") + + +def test_cli_no_args_shows_usage(capfd): + cli.cli(w("pricehist")) + out, err = capfd.readouterr() + assert "usage: pricehist" in out + assert "optional arguments:" in out + assert "commands:" in out + + +def test_cli_help_shows_usage_and_exits(capfd): + with pytest.raises(SystemExit) as e: + cli.cli(w("pricehist -h")) + assert e.value.code == 0 + out, err = capfd.readouterr() + assert "usage: pricehist" in out + assert "optional arguments:" in out + assert "commands:" in out + + +def test_cli_verbose(capfd, mocker): + cli.cli(w("pricehist --verbose")) + out, err = capfd.readouterr() + assert "Ended pricehist run at" in err + + +def test_cli_version(capfd): + cli.cli(w("pricehist --version")) + out, err = capfd.readouterr() + assert f"pricehist {__version__}\n" == out + + +def test_cli_sources(capfd): + cli.cli(w("pricehist sources")) + out, err = capfd.readouterr() + for source_id in sources.by_id.keys(): + assert source_id in out + + +def test_cli_source(capfd): + expected = sources.by_id["ecb"].format_info() + "\n" + cli.cli(w("pricehist source ecb")) + out, err = capfd.readouterr() + assert out == expected + + +def test_cli_source_symbols(capfd, mocker): + sources.by_id["ecb"].symbols = mocker.MagicMock( + return_value=[("EUR/AUD", "Euro against Australian Dollar")] + ) + cli.cli(w("pricehist source ecb --symbols")) + out, err = capfd.readouterr() + assert out == "EUR/AUD Euro against Australian Dollar\n" + + +def test_cli_source_search(capfd, mocker): + sources.by_id["alphavantage"].search = mocker.MagicMock( + return_value=[("TSLA", "Tesla Inc, Equity, United States, USD")] + ) + cli.cli(w("pricehist source alphavantage --search TSLA")) + out, err = capfd.readouterr() + assert out == "TSLA Tesla Inc, Equity, United States, USD\n" + + +def test_cli_source_fetch(capfd, mocker): + formatted_result = "P 2021-01-01 00:00:00 BTC 24139.4648 EUR\n" + cli.fetch = mocker.MagicMock(return_value=formatted_result) + argv = w("pricehist fetch coindesk BTC/EUR -s 2021-01-01 -e 2021-01-01 -o ledger") + cli.cli(argv) + out, err = capfd.readouterr() + assert out == formatted_result + + +def test_cli_source_fetch_invalid_start(capfd, mocker): + argv = w("pricehist fetch coindesk BTC/EUR -s 2021-01-01 -e 2020-12-01") + with pytest.raises(SystemExit) as e: + cli.cli(argv) + assert e.value.code != 0 + out, err = capfd.readouterr() + assert "end date '2020-12-01' preceeds the start date" in err + + +def test_cli_source_fetch_invalid_type(capfd, mocker): + argv = w("pricehist fetch coindesk BTC/EUR -t notype") + with pytest.raises(SystemExit) as e: + cli.cli(argv) + assert e.value.code != 0 + out, err = capfd.readouterr() + assert "price type 'notype' is not recognized" in err + + +def test_cli_source_fetch_sets_source_defaults(mocker): + cli.fetch = mocker.MagicMock(return_value="") + cli.cli(w("pricehist fetch coindesk BTC/EUR")) + captured_series = cli.fetch.call_args.args[0] + assert captured_series.start == sources.by_id["coindesk"].start() + assert captured_series.type == sources.by_id["coindesk"].types()[0] + + +def test_cli_source_fetch_normalizes_symbols(mocker): + cli.fetch = mocker.MagicMock(return_value="") + cli.cli(w("pricehist fetch coindesk btc/eur")) + captured_series = cli.fetch.call_args.args[0] + assert captured_series.base == "BTC" + assert captured_series.quote == "EUR" + + +def test_cli_source_fetch_handles_brokenpipeerror(caplog, mocker): + cli.fetch = mocker.MagicMock(side_effect=BrokenPipeError()) + cli.cli(w("pricehist fetch coindesk BTC/EUR --verbose")) + assert any( + [ + "DEBUG" == r.levelname and "output pipe was closed early" in r.message + for r in caplog.records + ] + )