From 08c3209dbc06329b4566ddd8d1dd7ab1c7e28ed7 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 24 Jul 2020 11:09:30 -0300 Subject: [PATCH] Add CI config for Gitlab CI integration tests (bash and pytest) --- .gitlab-ci.yml | 26 ++++ tests/.gitignore | 1 + tests/__init__.py | 14 ++ tests/components/__init__.py | 0 tests/components/bank.py | 43 ++++++ tests/components/config.py | 62 ++++++++ tests/components/exchange.py | 53 +++++++ tests/components/taler_service.py | 18 +++ tests/components/template.ini | 228 ++++++++++++++++++++++++++++++ tests/components/wallet.py | 50 +++++++ tests/conftest.py | 30 ++++ tests/requirements.txt | 4 + tests/test_exchange_management.py | 18 +++ tests/test_withdrawal.py | 120 ++++++++++++++++ 14 files changed, 667 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 tests/.gitignore create mode 100644 tests/__init__.py create mode 100644 tests/components/__init__.py create mode 100644 tests/components/bank.py create mode 100644 tests/components/config.py create mode 100644 tests/components/exchange.py create mode 100644 tests/components/taler_service.py create mode 100644 tests/components/template.ini create mode 100644 tests/components/wallet.py create mode 100644 tests/conftest.py create mode 100644 tests/requirements.txt create mode 100644 tests/test_exchange_management.py create mode 100644 tests/test_withdrawal.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..9eef833c9 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,26 @@ +image: "registry.gitlab.com/gnu-taler/docker-taler-ci:latest" + +before_script: + - pg_ctlcluster 12 main start + +integration_tests_legacy: + script: + - cd integrationtests + - ./test-base.sh + - ./test-double-link.sh + - ./test-double-spend.sh + - ./test-recoup.sh + - ./test-refund.sh + - ./test-retries.sh + - ./test-withdrawal.sh + allow_failure: true + +integration_tests: + script: + - ./bootstrap + - ./configure + - make install + - pytest -rP tests/ + +after_script: + - pg_ctlcluster 12 main stop diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 000000000..ed8ebf583 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..a01ed07c2 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,14 @@ +from taler.util.amount import Amount + + +def check_single_balance(balances, available, pending_in="TESTKUDOS:0", pending_out="TESTKUDOS:0", + has_pending=False): + assert len(balances) == 1 + assert balances[0]["available"] == available + assert balances[0]["pendingIncoming"] == pending_in + assert balances[0]["pendingOutgoing"] == pending_out + assert balances[0]["hasPendingTransactions"] == has_pending + + +def json_to_amount(d): + return Amount(d["currency"], d["value"], d["fraction"]) diff --git a/tests/components/__init__.py b/tests/components/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/components/bank.py b/tests/components/bank.py new file mode 100644 index 000000000..707edbfc4 --- /dev/null +++ b/tests/components/bank.py @@ -0,0 +1,43 @@ +import os +from subprocess import run + +import psutil + +from .taler_service import TalerService + + +class Bank(TalerService): + + def __init__(self, config, watcher_getter, request): + super().__init__(config, watcher_getter, request) + + # get localhost port and store bank URL + r = run(["taler-config", "-c", config.conf, "-s", "BANK", "-o", "HTTP_PORT"], + check=True, text=True, capture_output=True) + self.url = "http://localhost:%s" % r.stdout.rstrip() + + def start(self): + db = "postgres:///%s" % self.config.db + log_path = os.path.join(self.config.tmpdir, "bank.log") + log_file = open(log_path, 'w') + self.watcher_getter( + name='taler-bank-manage-testing', + arguments=[self.config.conf, db, 'serve-http'], + checker=self.test_process, + kwargs=dict(stderr=log_file, stdout=log_file), + request=self.request, # Needed for the correct execution order of finalizers + ) + + def close_log(): + log_file.close() + + self.request.addfinalizer(close_log) + + # Alternative way to check if the bank came up. + # Testing the URL has the issue that on the CI, django keeps closing the connection. + @staticmethod + def test_process(): + for p in psutil.process_iter(['name', 'cmdline']): + if p.info["name"] == "uwsgi" and p.info["cmdline"][-1] == "talerbank.wsgi": + return True + return False diff --git a/tests/components/config.py b/tests/components/config.py new file mode 100644 index 000000000..c683a11fa --- /dev/null +++ b/tests/components/config.py @@ -0,0 +1,62 @@ +import logging +import os +from shutil import copyfile +from subprocess import run + + +class Config: + + def __init__(self, request, tmpdir, worker_id): + self.tmpdir = tmpdir.strpath + self.data_home = os.path.join(self.tmpdir, 'data') + + # workaround for https://github.com/pytest-dev/pytest-services/issues/37 + logger = logging.getLogger( + '[{worker_id}] {name}'.format(name="pytest_services.log", worker_id=worker_id)) + logger.handlers.clear() + + # copy config file from template + self.conf = tmpdir.join("test.conf").strpath + template = os.path.join(os.path.dirname(__file__), 'template.ini') + copyfile(template, self.conf) + + # set TALER_HOME base dir + config_cmd = ["taler-config", "-c", self.conf] + run(config_cmd + ["-s", "PATHS", "-o", "TALER_HOME", "-V", self.tmpdir], check=True) + + # get path of exchange private key file and create key pair + config_cmd = ["taler-config", "-c", self.conf] + r = run(config_cmd + ["-f", "-s", "EXCHANGE", "-o", "MASTER_PRIV_FILE"], + capture_output=True, check=True, text=True) + master_priv_file = r.stdout.rstrip() + master_priv_dir = os.path.dirname(master_priv_file) + os.makedirs(master_priv_dir) + run(["gnunet-ecc", "-g1", master_priv_file], check=True, capture_output=True) + r = run(["gnunet-ecc", "-p", master_priv_file], check=True, capture_output=True, text=True) + self.master_pub = r.stdout.rstrip() + + # write exchange public key into config + run(config_cmd + ["-s", "exchange", + "-o", "MASTER_PUBLIC_KEY", + "-V", self.master_pub], check=True) + run(config_cmd + ["-s", "merchant-exchange-default", + "-o", "MASTER_KEY", + "-V", self.master_pub], check=True) + + # write DB name into config + self.db = "test-db" + db_uri = "postgres:///" + self.db + run(config_cmd + ["-s", "exchangedb-postgres", "-o", "CONFIG", "-V", db_uri], check=True) + run(config_cmd + ["-s", "auditordb-postgres", "-o", "CONFIG", "-V", db_uri], check=True) + run(config_cmd + ["-s", "merchantdb-postgres", "-o", "CONFIG", "-V", db_uri], check=True) + run(config_cmd + ["-s", "bank", "-o", "database", "-V", db_uri], check=True) + + # create new DB + run(["dropdb", self.db], capture_output=True) + run(["createdb", self.db], check=True) + + # drop DB when test ends + def finalize(): + run(["dropdb", self.db], capture_output=True) + + request.addfinalizer(finalize) diff --git a/tests/components/exchange.py b/tests/components/exchange.py new file mode 100644 index 000000000..9e804a65e --- /dev/null +++ b/tests/components/exchange.py @@ -0,0 +1,53 @@ +import os +from subprocess import run + +from .taler_service import TalerService + + +class Exchange(TalerService): + + def __init__(self, config, watcher_getter, request): + super().__init__(config, watcher_getter, request) + + # get own URL from config + r = run(["taler-config", "-c", config.conf, "-s", "EXCHANGE", "-o", "BASE_URL"], + check=True, text=True, capture_output=True) + self.url = r.stdout.rstrip() + + # get and create directory for terms of service + r = run(["taler-config", "-c", config.conf, "-s", "EXCHANGE", "-o", "TERMS_DIR"], + check=True, text=True, capture_output=True) + self.terms_dir = r.stdout.rstrip().replace("${TALER_DATA_HOME}", config.data_home) + terms_dir_en = os.path.join(self.terms_dir, 'en') + os.makedirs(terms_dir_en) + + # get eTag and create ToS file for it + r = run(["taler-config", "-c", config.conf, "-s", "EXCHANGE", "-o", "TERMS_ETAG"], + check=True, text=True, capture_output=True) + self.terms_etag = r.stdout.rstrip() + self.tos = "ToS Foo Bar\n" + with open(os.path.join(terms_dir_en, "%s.txt" % self.terms_etag), 'w') as f: + f.write(self.tos) + + def start(self): + run(["taler-exchange-dbinit", "-c", self.config.conf], check=True) + run(["taler-exchange-wire", "-c", self.config.conf], check=True) + run(["taler-exchange-keyup", "-c", self.config.conf, + "-L", "INFO", + "-o", os.path.join(self.config.tmpdir, "e2a.dat") + ], check=True, capture_output=True) + log_path = os.path.join(self.config.tmpdir, "exchange.log") + self.watcher_getter( + name='taler-exchange-httpd', + arguments=['-c', self.config.conf, '-l', log_path], + checker=self.test_url, + request=self.request, # Needed for the correct execution order of finalizers + ) + # the wirewatch is needed for interaction with the bank + log_wirewatch_path = os.path.join(self.config.tmpdir, "exchange-wirewatch.log") + self.watcher_getter( + name='taler-exchange-wirewatch', + arguments=['-c', self.config.conf, '-l', log_wirewatch_path], + checker=lambda: True, # no need to wait for this to come up + request=self.request, # Needed for the correct execution order of finalizers + ) diff --git a/tests/components/taler_service.py b/tests/components/taler_service.py new file mode 100644 index 000000000..9fce9395c --- /dev/null +++ b/tests/components/taler_service.py @@ -0,0 +1,18 @@ +import requests +from requests.exceptions import ConnectionError + + +class TalerService: + + def __init__(self, config, watcher_getter, request): + self.config = config + self.watcher_getter = watcher_getter + self.request = request + + def test_url(self): + try: + requests.get(self.url, timeout=3) + except ConnectionError as e: + return False + else: + return True diff --git a/tests/components/template.ini b/tests/components/template.ini new file mode 100644 index 000000000..32089636d --- /dev/null +++ b/tests/components/template.ini @@ -0,0 +1,228 @@ +[exchange] +KEYDIR = ${TALER_DATA_HOME}/exchange/live-keys/ +REVOCATION_DIR = ${TALER_DATA_HOME}/exchange/revocations/ +MAX_KEYS_CACHING = forever +DB = postgres +MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv +SERVE = tcp +UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http +UNIXPATH_MODE = 660 +PORT = 8081 +BASE_URL = http://localhost:8081/ +SIGNKEY_DURATION = 4 weeks +LEGAL_DURATION = 2 years +LOOKAHEAD_SIGN = 32 weeks 1 day +LOOKAHEAD_PROVIDE = 4 weeks 1 day +TERMS_ETAG = test_etag +TERMS_DIR = ${TALER_DATA_HOME}/exchange/terms + +[merchant] +SERVE = tcp +PORT = 9966 +UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http +UNIXPATH_MODE = 660 +DEFAULT_WIRE_FEE_AMORTIZATION = 1 +DB = postgres +WIREFORMAT = default +# Set very low, so we can be sure that the database generated +# will contain wire transfers "ready" for the aggregator. +WIRE_TRANSFER_DELAY = 1 minute +DEFAULT_PAY_DEADLINE = 1 day +DEFAULT_MAX_DEPOSIT_FEE = TESTKUDOS:0.1 +KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv +DEFAULT_MAX_WIRE_FEE = TESTKUDOS:0.10 + +# Ensure that merchant reports EVERY deposit confirmation to auditor +FORCE_AUDIT = YES + +[instance-default] +KEYFILE = ${TALER_DATA_HOME}/merchant/default.priv +NAME = Merchant Inc. +TIP_EXCHANGE = http://localhost:8081/ +# TODO necessary to specify a different key here? +TIP_RESERVE_PRIV_FILENAME = ${TALER_DATA_HOME}/merchant/default.priv + +[auditor] +DB = postgres +AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv +SERVE = tcp +UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http +UNIXPATH_MODE = 660 +PORT = 8083 +AUDITOR_URL = http://localhost:8083/ +TINY_AMOUNT = TESTKUDOS:0.01 + +[PATHS] +TALER_DATA_HOME = $TALER_HOME/data/ +TALER_CONFIG_HOME = $TALER_HOME/config/ +TALER_CACHE_HOME = $TALER_HOME/cache/ +TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/taler-system-runtime/ + +[bank] +DATABASE = postgres:///taler-auditor-basedb +MAX_DEBT = TESTKUDOS:50.0 +MAX_DEBT_BANK = TESTKUDOS:100000.0 +HTTP_PORT = 8082 +SUGGESTED_EXCHANGE = http://localhost:8081/ +SUGGESTED_EXCHANGE_PAYTO = payto://x-taler-bank/localhost/2 +ALLOW_REGISTRATIONS = YES + +[exchangedb] +AUDITOR_BASE_DIR = ${TALER_DATA_HOME}/auditors/ +WIREFEE_BASE_DIR = ${TALER_DATA_HOME}/exchange/wirefees/ +IDLE_RESERVE_EXPIRATION_TIME = 4 weeks +LEGAL_RESERVE_EXPIRATION_TIME = 7 years + +[exchange_keys] +signkey_duration = 4 weeks +legal_duration = 2 years +lookahead_sign = 32 weeks 1 day +lookahead_provide = 4 weeks 1 day + +[taler] +CURRENCY = TESTKUDOS +CURRENCY_ROUND_UNIT = TESTKUDOS:0.01 + +[exchange-account-1] +WIRE_RESPONSE = ${TALER_DATA_HOME}/exchange/account-1.json +PAYTO_URI = payto://x-taler-bank/localhost/Exchange +enable_debit = yes +enable_credit = yes +WIRE_GATEWAY_URL = "http://localhost:8082/taler-wire-gateway/Exchange/" +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + +[merchant-account-merchant] +PAYTO_URI = payto://x-taler-bank/localhost/42 +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/merchant/account-3.json +HONOR_default = YES +ACTIVE_default = YES + +[fees-x-taler-bank] +wire-fee-2020 = TESTKUDOS:0.01 +closing-fee-2020 = TESTKUDOS:0.01 +wire-fee-2021 = TESTKUDOS:0.01 +closing-fee-2021 = TESTKUDOS:0.01 +wire-fee-2022 = TESTKUDOS:0.01 +closing-fee-2022 = TESTKUDOS:0.01 +wire-fee-2023 = TESTKUDOS:0.01 +closing-fee-2023 = TESTKUDOS:0.01 +wire-fee-2024 = TESTKUDOS:0.01 +closing-fee-2024 = TESTKUDOS:0.01 +wire-fee-2025 = TESTKUDOS:0.01 +closing-fee-2025 = TESTKUDOS:0.01 +wire-fee-2026 = TESTKUDOS:0.01 +closing-fee-2026 = TESTKUDOS:0.01 +wire-fee-2027 = TESTKUDOS:0.01 +closing-fee-2027 = TESTKUDOS:0.01 +wire-fee-2028 = TESTKUDOS:0.01 +closing-fee-2028 = TESTKUDOS:0.01 + +[merchant-instance-wireformat-default] +TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/merchant/wire/tutorial.json + +[merchant-exchange-default] +EXCHANGE_BASE_URL = http://localhost:8081/ +CURRENCY = TESTKUDOS + +[payments-generator] +currency = TESTKUDOS +instance = default +bank = http://localhost:8082/ +merchant = http://localhost:9966/ +exchange_admin = http://localhost:18080/ +exchange-admin = http://localhost:18080/ +exchange = http://localhost:8081/ + +[coin_kudos_ct_1] +value = TESTKUDOS:0.01 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.01 +fee_deposit = TESTKUDOS:0.01 +fee_refresh = TESTKUDOS:0.01 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 + +[coin_kudos_ct_10] +value = TESTKUDOS:0.10 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.01 +fee_deposit = TESTKUDOS:0.01 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 + +[coin_kudos_1] +value = TESTKUDOS:1 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.02 +fee_deposit = TESTKUDOS:0.02 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 + +[coin_kudos_2] +value = TESTKUDOS:2 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.03 +fee_deposit = TESTKUDOS:0.03 +fee_refresh = TESTKUDOS:0.04 +fee_refund = TESTKUDOS:0.02 +rsa_keysize = 1024 + +[coin_kudos_4] +value = TESTKUDOS:4 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.03 +fee_deposit = TESTKUDOS:0.03 +fee_refresh = TESTKUDOS:0.04 +fee_refund = TESTKUDOS:0.02 +rsa_keysize = 1024 + +[coin_kudos_5] +value = TESTKUDOS:5 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.01 +fee_deposit = TESTKUDOS:0.01 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 + +[coin_kudos_8] +value = TESTKUDOS:8 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.05 +fee_deposit = TESTKUDOS:0.02 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.04 +rsa_keysize = 1024 + +[coin_kudos_10] +value = TESTKUDOS:10 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.01 +fee_deposit = TESTKUDOS:0.01 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 + +[benchmark] +BANK_DETAILS = bank_details.json +MERCHANT_DETAILS = merchant_details.json diff --git a/tests/components/wallet.py b/tests/components/wallet.py new file mode 100644 index 000000000..77099ab88 --- /dev/null +++ b/tests/components/wallet.py @@ -0,0 +1,50 @@ +import json +import os +from subprocess import run + + +class Wallet: + + def __init__(self, config): + self.db = os.path.join(config.tmpdir, "wallet-db.json") + self.arg_db = "--wallet-db=%s" % self.db + self.log_path = os.path.join(config.tmpdir, "wallet.log") + + def cmd(self, command, request=None): + if request is None: + request = dict() + request = json.dumps(request) + r = run(["taler-wallet-cli", self.arg_db, "api", command, request], + timeout=10, text=True, capture_output=True) + self.write_to_log(r.stderr) + if r.returncode != 0: + print(r) + assert r.returncode == 0 + json_r = json.loads(r.stdout) + if json_r["isError"]: + print(r) + assert not json_r["isError"] + if "result" not in json_r: + # TODO should there not always be a "result"? + return None + return json_r["result"] + + def testing_withdraw(self, amount, exchange_url, bank_url): + r = run(["taler-wallet-cli", self.arg_db, "--no-throttle", "testing", "withdraw", + "-a", amount, + "-e", exchange_url, + "-b", bank_url + ], timeout=10, check=True, text=True, capture_output=True) + self.write_to_log(r.stderr) + + def gen_withdraw_uri(self, amount, bank_url): + r = run(["taler-wallet-cli", self.arg_db, "testing", "gen-withdraw-uri", + "-a", amount, + "-b", bank_url + ], timeout=10, check=True, text=True, capture_output=True) + self.write_to_log(r.stderr) + return r.stdout.rstrip() + + def write_to_log(self, data): + with open(self.log_path, "a") as f: + f.write(data) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..1922d1d40 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,30 @@ +import pytest + +from tests.components.bank import Bank +from tests.components.config import Config +from tests.components.exchange import Exchange +from tests.components.wallet import Wallet + + +@pytest.fixture +def config(watcher_getter, request, tmpdir, worker_id): + return Config(request, tmpdir, worker_id) + + +@pytest.fixture +def exchange(watcher_getter, request, config): + exchange = Exchange(config, watcher_getter, request) + exchange.start() + return exchange + + +@pytest.fixture +def bank(watcher_getter, request, config): + bank = Bank(config, watcher_getter, request) + bank.start() + return bank + + +@pytest.fixture +def wallet(watcher_getter, config): + return Wallet(config) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 000000000..6f2484b78 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,4 @@ +pytest==5.4.* +pytest-services==2.1.* +taler-util==0.6.* +psutil==5.7.* \ No newline at end of file diff --git a/tests/test_exchange_management.py b/tests/test_exchange_management.py new file mode 100644 index 000000000..5e0462944 --- /dev/null +++ b/tests/test_exchange_management.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + + +def test_exchanges(exchange, wallet): + # list of exchanges is initially empty + result = wallet.cmd("listExchanges") + assert not result["exchanges"] + + # adding an exchange works + result = wallet.cmd("addExchange", {"exchangeBaseUrl": exchange.url}) + assert not result # result is empty + + # list includes added exchange + result = wallet.cmd("listExchanges") + e = result["exchanges"][0] + assert e["exchangeBaseUrl"] == exchange.url + assert e["currency"] == "TESTKUDOS" + assert len(e["paytoUris"]) >= 1 diff --git a/tests/test_withdrawal.py b/tests/test_withdrawal.py new file mode 100644 index 000000000..8a68807b6 --- /dev/null +++ b/tests/test_withdrawal.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 + +from taler.util.amount import Amount + +from tests import check_single_balance + + +def test_withdrawal(exchange, bank, wallet): + # assert that we start with no transactions + result = wallet.cmd("getTransactions") + assert not result["transactions"] + + # test withdrawal + amount_raw = "TESTKUDOS:5" + wallet.testing_withdraw(amount_raw, exchange.url, bank.url) + + # check that balance is correct + result = wallet.cmd("getBalances") + amount_effective = Amount("TESTKUDOS", 4, 84000000).stringify() + check_single_balance(result["balances"], amount_effective) + + # assert that withdrawal shows up properly in transactions + result = wallet.cmd("getTransactions") + assert len(result["transactions"]) == 1 + transaction = result["transactions"][0] + assert transaction["type"] == "withdrawal" + assert transaction["amountEffective"] == amount_effective + assert transaction["amountRaw"] == amount_raw + assert transaction["exchangeBaseUrl"] == exchange.url + assert not transaction["pending"] + withdrawal_details = transaction["withdrawalDetails"] + assert withdrawal_details["type"] == "manual-transfer" + payto_list = ["payto://x-taler-bank/localhost/Exchange"] + assert withdrawal_details["exchangePaytoUris"] == payto_list + + # get a withdrawal URI + uri = wallet.gen_withdraw_uri(amount_raw, bank.url) + assert uri.startswith("taler+http://withdraw") + + # get withdrawal details from URI + result = wallet.cmd("getWithdrawalDetailsForUri", {"talerWithdrawUri": uri}) + assert result["amount"] == amount_raw + assert result["defaultExchangeBaseUrl"] == exchange.url + assert len(result["possibleExchanges"]) == 1 + assert result["possibleExchanges"][0]["exchangeBaseUrl"] == exchange.url + assert result["possibleExchanges"][0]["currency"] == "TESTKUDOS" + assert result["possibleExchanges"][0]["paytoUris"] == payto_list + + # check withdrawal details for amount + request = {"exchangeBaseUrl": exchange.url, "amount": amount_raw} + result = wallet.cmd("getWithdrawalDetailsForAmount", request) + assert result["amountRaw"] == amount_raw + assert result["amountEffective"] == amount_effective + assert result["paytoUris"] == payto_list + assert not result["tosAccepted"] + + # get ToS + result = wallet.cmd("getExchangeTos", {"exchangeBaseUrl": exchange.url}) + assert result["currentEtag"] == exchange.terms_etag + assert result["tos"] == exchange.tos + + # accept ToS + request = {"exchangeBaseUrl": exchange.url, "etag": exchange.terms_etag} + wallet.cmd("setExchangeTosAccepted", request) + + # check that ToS are now shown as accepted + request = {"exchangeBaseUrl": exchange.url, "amount": amount_raw} + result = wallet.cmd("getWithdrawalDetailsForAmount", request) + assert result["tosAccepted"] + + # accept withdrawal + request = {"exchangeBaseUrl": exchange.url, "talerWithdrawUri": uri} + result = wallet.cmd("acceptBankIntegratedWithdrawal", request) + assert result["confirmTransferUrl"].startswith(bank.url + "/confirm-withdrawal/") + confirm_url = result["confirmTransferUrl"] + + # check that balance is correct + result = wallet.cmd("getBalances") + # TODO pendingIncoming and hasPendingTransactions are wrong, right? + print(result) + # check_single_balance(result["balances"], amount_effective, amount_effective, has_pending=True) + + # assert that 2nd withdrawal shows up properly in transactions + result = wallet.cmd("getTransactions") + assert len(result["transactions"]) == 2 + transaction = result["transactions"][0] + assert transaction["type"] == "withdrawal" + assert transaction["amountEffective"] == amount_effective + assert transaction["amountRaw"] == amount_raw + assert transaction["exchangeBaseUrl"] == exchange.url + assert transaction["pending"] + withdrawal_details = transaction["withdrawalDetails"] + assert withdrawal_details["type"] == "taler-bank-integration-api" + assert not withdrawal_details["confirmed"] + assert withdrawal_details["bankConfirmationUrl"] == confirm_url + + # new withdrawal is newer than old one + timestamp0 = result["transactions"][0]["timestamp"]["t_ms"] + timestamp1 = result["transactions"][1]["timestamp"]["t_ms"] + assert timestamp0 > timestamp1 + + # one more manual withdrawal + request = {"exchangeBaseUrl": exchange.url, "amount": amount_raw} + result = wallet.cmd("acceptManualWithdrawal", request) + assert len(result["exchangePaytoUris"]) == 1 + result["exchangePaytoUris"][0].startswith(payto_list[0]) + + # check that balance is correct + result = wallet.cmd("getBalances") + # TODO pendingIncoming and hasPendingTransactions are wrong, right? + print(result) + # check_single_balance(result["balances"], amount_effective, TODO, has_pending=True) + + # assert that 3nd withdrawal shows up properly in transactions + result = wallet.cmd("getTransactions") + # TODO where is the manual withdrawal!?? + # assert len(result["transactions"]) == 3 + for t in result["transactions"]: + print(t) + print()