summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-07-24 11:09:30 -0300
committerTorsten Grote <t@grobox.de>2020-07-28 15:42:39 -0300
commit08c3209dbc06329b4566ddd8d1dd7ab1c7e28ed7 (patch)
tree794658b2b9dfc3a39d8e374feae7e03919742f63
parent503c35d5f5972551cffa1fecb8b7b463503cc662 (diff)
downloadwallet-core-08c3209dbc06329b4566ddd8d1dd7ab1c7e28ed7.tar.gz
wallet-core-08c3209dbc06329b4566ddd8d1dd7ab1c7e28ed7.tar.bz2
wallet-core-08c3209dbc06329b4566ddd8d1dd7ab1c7e28ed7.zip
Add CI config for Gitlab CI integration tests (bash and pytest)
-rw-r--r--.gitlab-ci.yml26
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/__init__.py14
-rw-r--r--tests/components/__init__.py0
-rw-r--r--tests/components/bank.py43
-rw-r--r--tests/components/config.py62
-rw-r--r--tests/components/exchange.py53
-rw-r--r--tests/components/taler_service.py18
-rw-r--r--tests/components/template.ini228
-rw-r--r--tests/components/wallet.py50
-rw-r--r--tests/conftest.py30
-rw-r--r--tests/requirements.txt4
-rw-r--r--tests/test_exchange_management.py18
-rw-r--r--tests/test_withdrawal.py120
14 files changed, 667 insertions, 0 deletions
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
--- /dev/null
+++ b/tests/components/__init__.py
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()