summaryrefslogtreecommitdiff
path: root/src/testing
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-04-22 19:59:34 +0200
committerChristian Grothoff <christian@grothoff.org>2020-04-22 19:59:34 +0200
commite357b735bf09bcdb201841885e79e9f946582912 (patch)
treeba9f47191fd3b07cb226e3412055fc98f483a1e1 /src/testing
parent7aaeee0914057eeb64bf6af80b54d7d9e58d1b39 (diff)
downloadmerchant-e357b735bf09bcdb201841885e79e9f946582912.tar.gz
merchant-e357b735bf09bcdb201841885e79e9f946582912.tar.bz2
merchant-e357b735bf09bcdb201841885e79e9f946582912.zip
move libtalermerchanttesting and test cases to src/testing/
Diffstat (limited to 'src/testing')
-rw-r--r--src/testing/Makefile.am111
-rw-r--r--src/testing/reserve_dtip.priv1
-rw-r--r--src/testing/reserve_tip.privbin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant.priv1
-rw-r--r--src/testing/test_merchant_api.c1012
-rw-r--r--src/testing/test_merchant_api.conf206
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json4
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/merchant/account-3.json1
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/merchant/default.priv1
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/merchant/dtip.priv1
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/merchant/nulltip.priv2
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/merchant/reserve/dtip.privbin0 -> 32 bytes
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/merchant/reserve/nulltip.priv1
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/merchant/reserve/tip.priv1
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/merchant/tip.priv1
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/merchant/tor.priv1
-rw-r--r--src/testing/test_merchant_api_home/.config/taler/test.json8
-rw-r--r--src/testing/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/testing/test_merchant_api_home/.local/share/taler/merchant/merchant.priv1
-rw-r--r--src/testing/test_merchant_api_proxy_exchange.conf30
-rw-r--r--src/testing/test_merchant_api_proxy_merchant.conf31
-rw-r--r--src/testing/test_merchant_api_twisted.c950
-rw-r--r--src/testing/test_merchant_api_twisted.conf18
-rw-r--r--src/testing/testing_api_cmd_check_payment.c495
-rw-r--r--src/testing/testing_api_cmd_config.c164
-rw-r--r--src/testing/testing_api_cmd_history.c359
-rw-r--r--src/testing/testing_api_cmd_pay.c875
-rw-r--r--src/testing/testing_api_cmd_pay_abort.c595
-rw-r--r--src/testing/testing_api_cmd_pay_abort_refund.c248
-rw-r--r--src/testing/testing_api_cmd_poll_payment.c491
-rw-r--r--src/testing/testing_api_cmd_proposal.c401
-rw-r--r--src/testing/testing_api_cmd_proposal_lookup.c311
-rw-r--r--src/testing/testing_api_cmd_refund_increase.c236
-rw-r--r--src/testing/testing_api_cmd_refund_lookup.c457
-rw-r--r--src/testing/testing_api_cmd_rewind.c120
-rw-r--r--src/testing/testing_api_cmd_tip_authorize.c369
-rw-r--r--src/testing/testing_api_cmd_tip_pickup.c437
-rw-r--r--src/testing/testing_api_cmd_tip_query.c297
-rw-r--r--src/testing/testing_api_cmd_track_transaction.c295
-rw-r--r--src/testing/testing_api_cmd_track_transfer.c322
-rw-r--r--src/testing/testing_api_helpers.c195
-rw-r--r--src/testing/testing_api_trait_hash.c126
-rw-r--r--src/testing/testing_api_trait_merchant_sig.c77
-rw-r--r--src/testing/testing_api_trait_planchet.c75
-rw-r--r--src/testing/testing_api_trait_refund_entry.c78
-rw-r--r--src/testing/testing_api_trait_string.c136
-rw-r--r--src/testing/tor_merchant.priv1
47 files changed, 9543 insertions, 0 deletions
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
new file mode 100644
index 00000000..48053c9b
--- /dev/null
+++ b/src/testing/Makefile.am
@@ -0,0 +1,111 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/backend
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libtalermerchanttesting.la
+
+libtalermerchanttesting_la_LDFLAGS = \
+ -version-info 2:0:0 \
+ -no-undefined
+
+libtalermerchanttesting_la_SOURCES = \
+ testing_api_cmd_check_payment.c \
+ testing_api_cmd_config.c \
+ testing_api_cmd_history.c \
+ testing_api_cmd_pay.c \
+ testing_api_cmd_pay_abort.c \
+ testing_api_cmd_pay_abort_refund.c \
+ testing_api_cmd_poll_payment.c \
+ testing_api_cmd_proposal.c \
+ testing_api_cmd_proposal_lookup.c \
+ testing_api_cmd_refund_increase.c \
+ testing_api_cmd_refund_lookup.c \
+ testing_api_cmd_rewind.c \
+ testing_api_cmd_tip_authorize.c \
+ testing_api_cmd_tip_pickup.c \
+ testing_api_cmd_tip_query.c \
+ testing_api_cmd_track_transaction.c \
+ testing_api_cmd_track_transfer.c \
+ testing_api_helpers.c \
+ testing_api_trait_merchant_sig.c \
+ testing_api_trait_string.c \
+ testing_api_trait_hash.c \
+ testing_api_trait_planchet.c \
+ testing_api_trait_refund_entry.c
+
+libtalermerchanttesting_la_LIBADD = \
+ libtalermerchant.la \
+ -ltalerexchange \
+ -ltalerjson \
+ -ltalerutil \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ -ltalertesting \
+ $(XLIB)
+
+
+if HAVE_TALERFAKEBANK
+check_PROGRAMS = \
+ test_merchant_api
+
+if HAVE_TWISTER
+check_PROGRAMS += test_merchant_api_twisted
+endif
+endif
+
+TESTS = \
+ $(check_PROGRAMS)
+
+test_merchant_api_twisted_SOURCES = \
+ test_merchant_api_twisted.c
+test_merchant_api_twisted_LDADD = \
+ $(top_srcdir)/src/backenddb/libtalermerchantdb.la \
+ libtalermerchant.la \
+ $(LIBGCRYPT_LIBS) \
+ -ltalertesting \
+ -ltalermerchanttesting \
+ -ltalertwistertesting \
+ -ltalerfakebank \
+ -ltalerbank \
+ -ltalerexchange \
+ -ltalerjson \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ -ltalertwister
+
+test_merchant_api_SOURCES = \
+ test_merchant_api.c
+test_merchant_api_LDADD = \
+ $(top_srcdir)/src/backenddb/libtalermerchantdb.la \
+ libtalermerchant.la \
+ $(LIBGCRYPT_LIBS) \
+ -ltalertesting \
+ -ltalermerchanttesting \
+ -ltalerfakebank \
+ -ltalerbank \
+ -ltalerexchange \
+ -ltalerjson \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson
+
+EXTRA_DIST = \
+ test_merchant_api.conf \
+ test_merchant_api_twisted.conf \
+ test_merchant_api_proxy_merchant.conf \
+ test_merchant_api_proxy_exchange.conf \
+ test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv \
+ test_merchant_api_home/.config/taler/exchange/account-2.json \
+ test_merchant.priv
diff --git a/src/testing/reserve_dtip.priv b/src/testing/reserve_dtip.priv
new file mode 100644
index 00000000..d7fae398
--- /dev/null
+++ b/src/testing/reserve_dtip.priv
@@ -0,0 +1 @@
+A?*K4K31b'uyD;ȃC \ No newline at end of file
diff --git a/src/testing/reserve_tip.priv b/src/testing/reserve_tip.priv
new file mode 100644
index 00000000..3cd75052
--- /dev/null
+++ b/src/testing/reserve_tip.priv
Binary files differ
diff --git a/src/testing/test_merchant.priv b/src/testing/test_merchant.priv
new file mode 100644
index 00000000..9c18c358
--- /dev/null
+++ b/src/testing/test_merchant.priv
@@ -0,0 +1 @@
+`&-./ jxGݢO:6l,ζXT4 \ No newline at end of file
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
new file mode 100644
index 00000000..3a203fce
--- /dev/null
+++ b/src/testing/test_merchant_api.c
@@ -0,0 +1,1012 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/test_merchant_api.c
+ * @brief testcase to test exchange's HTTP API interface
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include <taler/taler_bank_service.h>
+#include <taler/taler_fakebank_lib.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_error_codes.h>
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_merchant_api.conf"
+
+/**
+ * Exchange base URL. Could also be taken from config.
+ */
+#define EXCHANGE_URL "http://localhost:8081/"
+
+static const char *pickup_amounts_1[] = {"EUR:5", NULL};
+
+/**
+ * Payto URI of the customer (payer).
+ */
+static char *payer_payto;
+
+/**
+ * Payto URI of the exchange (escrow account).
+ */
+static char *exchange_payto;
+
+/**
+ * Payto URI of the merchant (receiver).
+ */
+static char *merchant_payto;
+
+/**
+ * Configuration of the bank.
+ */
+static struct TALER_TESTING_BankConfiguration bc;
+
+/**
+ * Configuration of the exchange.
+ */
+static struct TALER_TESTING_ExchangeConfiguration ec;
+
+/**
+ * Merchant base URL.
+ */
+static char *merchant_url;
+
+/**
+ * Merchant process.
+ */
+static struct GNUNET_OS_Process *merchantd;
+
+/**
+ * Map for #intern()
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *interned_strings;
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NAME "2"
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NAME "62"
+
+/**
+ * Account number of some other user.
+ */
+#define USER_ACCOUNT_NAME2 "63"
+
+/**
+ * Account number used by the merchant
+ */
+#define MERCHANT_ACCOUNT_NAME "3"
+
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+static struct TALER_TESTING_Command
+cmd_exec_wirewatch (char *label)
+{
+ return TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE);
+}
+
+
+/**
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_exec_aggregator (label "-aggregator", CONFIG_FILE), \
+ TALER_TESTING_cmd_exec_transfer (label "-transfer", CONFIG_FILE)
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ * @param url exchange_url
+ */
+static struct TALER_TESTING_Command
+cmd_transfer_to_exchange (const char *label,
+ const char *amount)
+{
+ return TALER_TESTING_cmd_admin_add_incoming (label,
+ amount,
+ &bc.exchange_auth,
+ payer_payto);
+}
+
+
+static const char *
+intern (const char *str)
+{
+ struct GNUNET_HashCode hash;
+ const char *hs;
+
+ if (NULL == interned_strings)
+ interned_strings = GNUNET_CONTAINER_multihashmap_create (32, GNUNET_NO);
+ GNUNET_assert (NULL != interned_strings);
+ GNUNET_CRYPTO_hash (str, strlen (str), &hash);
+ hs = GNUNET_CONTAINER_multihashmap_get (interned_strings, &hash);
+ if (NULL != hs)
+ return hs;
+ hs = GNUNET_strdup (str);
+ GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (
+ interned_strings,
+ &hash,
+ (void *) hs,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ return hs;
+}
+
+
+#define BUF_SZ 512
+
+static const char *
+merchant_url_internal (const char *instance_id)
+{
+ char buf[BUF_SZ];
+
+ if (NULL == instance_id)
+ GNUNET_snprintf (buf,
+ BUF_SZ,
+ "%s",
+ merchant_url);
+ else
+ GNUNET_snprintf (buf,
+ BUF_SZ,
+ "%sinstances/%s/",
+ merchant_url,
+ instance_id);
+ return intern (buf);
+}
+
+
+static const char *
+merchant_url_external (const char *instance_id)
+{
+ char buf[BUF_SZ];
+ if (NULL == instance_id)
+ GNUNET_snprintf (buf,
+ BUF_SZ,
+ "%spublic/",
+ merchant_url);
+ else
+ GNUNET_snprintf (buf,
+ BUF_SZ,
+ "%spublic/instances/%s/",
+ merchant_url,
+ instance_id);
+ return intern (buf);
+}
+
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TALER_TESTING_Command pay[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ cmd_transfer_to_exchange ("create-reserve-1",
+ "EUR:10.02"),
+ /**
+ * Make a reserve exist,
+ * according to the previous
+ * transfer.
+ *///
+ cmd_exec_wirewatch ("wirewatch-1"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2",
+ "EUR:10.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-1"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ /**
+ * Check the reserve is depleted.
+ */
+ TALER_TESTING_cmd_status ("withdraw-status-1",
+ "create-reserve-1",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("create-proposal-1",
+ merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"1\",\
+ \"refund_deadline\": {\"t_ms\": 0},\
+ \"pay_deadline\": {\"t_ms\": \"never\" },\
+ \"amount\":\"EUR:5.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }"),
+ TALER_TESTING_cmd_check_payment ("check-payment-1",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-1",
+ GNUNET_NO),
+ TALER_TESTING_cmd_poll_payment_start ("poll-payment-1",
+ merchant_url,
+ "create-proposal-1",
+ NULL,
+ GNUNET_TIME_UNIT_MILLISECONDS),
+ TALER_TESTING_cmd_poll_payment_conclude ("poll-payment-conclude-1",
+ MHD_HTTP_OK,
+ "poll-payment-1",
+ GNUNET_NO),
+ TALER_TESTING_cmd_poll_payment_start ("poll-payment-2",
+ merchant_url,
+ "create-proposal-1",
+ NULL,
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_TESTING_cmd_check_payment_start ("check-payment-2",
+ merchant_url,
+ "create-proposal-1",
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_TESTING_cmd_pay ("deposit-simple",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-1",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+ TALER_TESTING_cmd_poll_payment_conclude ("poll-payment-conclude-2",
+ MHD_HTTP_OK,
+ "poll-payment-2",
+ GNUNET_YES),
+ TALER_TESTING_cmd_check_payment_conclude ("check-payment-conclude-2",
+ MHD_HTTP_OK,
+ "check-payment-2",
+ GNUNET_YES),
+ TALER_TESTING_cmd_pay_abort ("pay-abort-2",
+ merchant_url,
+ "deposit-simple",
+ MHD_HTTP_FORBIDDEN),
+ TALER_TESTING_cmd_pay ("replay-simple",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-1",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-1"),
+ CMD_EXEC_AGGREGATOR ("run-aggregator"),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-498c",
+ EXCHANGE_URL,
+ "EUR:4.98",
+ exchange_payto,
+ merchant_payto),
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-2"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command double_spending[] = {
+ TALER_TESTING_cmd_proposal ("create-proposal-2",
+ merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"2\",\
+ \"refund_deadline\": {\"t_ms\": 0},\
+ \"pay_deadline\": {\"t_ms\": \"never\" },\
+ \"amount\":\"EUR:5.0\",\
+ \"summary\": \"useful product\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }"),
+ TALER_TESTING_cmd_proposal_lookup ("fetch-proposal-2",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-2",
+ NULL),
+ TALER_TESTING_cmd_pay ("deposit-double-2",
+ merchant_url,
+ MHD_HTTP_CONFLICT,
+ "create-proposal-2",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+ TALER_TESTING_cmd_history ("history-0",
+ merchant_url,
+ MHD_HTTP_OK,
+ /**
+ * all records to be returned; setting date as 0 lets the
+ * interpreter set it as 'now' + one hour delta, just to
+ * make sure it surpasses the proposal's timestamp.
+ */GNUNET_TIME_UNIT_ZERO_ABS,
+ /**
+ * We only expect ONE result (create-proposal-1) to be
+ * included in /history response, because create-proposal-3
+ * did NOT go through because of double spending.
+ */1, // nresult
+ 10, // start
+ -10), // nrows
+ TALER_TESTING_cmd_end ()
+ };
+ struct TALER_TESTING_Command track[] = {
+ TALER_TESTING_cmd_merchant_track_transaction ("track-transaction-1",
+ merchant_url,
+ MHD_HTTP_OK,
+ "deposit-simple"),
+ TALER_TESTING_cmd_merchant_track_transfer ("track-transfer-1",
+ merchant_url,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c"),
+ TALER_TESTING_cmd_merchant_track_transfer ("track-transfer-again",
+ merchant_url,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c"),
+ cmd_transfer_to_exchange ("create-reserve-2",
+ "EUR:1"),
+ TALER_TESTING_cmd_admin_add_incoming_with_ref ("create-reserve-2b",
+ "EUR:4.01",
+ &bc.exchange_auth,
+ payer_payto,
+ "create-reserve-2"),
+ cmd_exec_wirewatch ("wirewatch-2"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2a",
+ "EUR:1",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-2"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2b",
+ "EUR:4.01",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-2"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-2",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_pay ("deposit-simple-2",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-2",
+ "withdraw-coin-2",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+ CMD_EXEC_AGGREGATOR ("run-aggregator-2"),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-498c-2",
+ EXCHANGE_URL,
+ "EUR:4.98",
+ exchange_payto,
+ merchant_payto),
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
+ TALER_TESTING_cmd_merchant_track_transfer ("track-transfer-2",
+ merchant_url,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c-2"),
+ TALER_TESTING_cmd_merchant_track_transfer ("track-transfer-2-again",
+ merchant_url,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c-2"),
+ TALER_TESTING_cmd_merchant_track_transaction ("track-transaction-2",
+ merchant_url,
+ MHD_HTTP_OK,
+ "deposit-simple-2"),
+ TALER_TESTING_cmd_history ("history-1",
+ merchant_url,
+ MHD_HTTP_OK,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ /**
+ * Now we expect BOTH contracts (create-proposal-{1,2})
+ * to be included in /history response, because
+ * create-proposal-2 has now been correctly paid.
+ */2,
+ 10,
+ -10),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command refund[] = {
+ cmd_transfer_to_exchange ("create-reserve-1r",
+ "EUR:10.02"),
+ /**
+ * Make a reserve exist, according to the previous transfer.
+ *///
+ cmd_exec_wirewatch ("wirewatch-1r"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2r",
+ "EUR:10.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-1r"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1r",
+ "create-reserve-1r",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2r",
+ "create-reserve-1r",
+ "EUR:5",
+ MHD_HTTP_OK),
+ /**
+ * Check the reserve is depleted.
+ */
+ TALER_TESTING_cmd_status ("withdraw-status-1r",
+ "create-reserve-1r",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("create-proposal-1r",
+ merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"1r\",\
+ \"refund_deadline\": {\"t_ms\": 0},\
+ \"pay_deadline\": {\"t_ms\": \"never\" },\
+ \"amount\":\"EUR:5.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }"),
+ TALER_TESTING_cmd_pay ("pay-for-refund-1r",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-1r",
+ "withdraw-coin-1r",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+ TALER_TESTING_cmd_poll_payment_start ("poll-payment-refund-1",
+ merchant_url,
+ "create-proposal-1r",
+ "EUR:0.0",
+ GNUNET_TIME_UNIT_MINUTES),
+ TALER_TESTING_cmd_refund_increase ("refund-increase-1r",
+ merchant_url,
+ "refund test",
+ "1r", /* order ID */
+ "EUR:0.1",
+ "EUR:0.01",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_poll_payment_conclude ("poll-payment-refund-conclude-1",
+ MHD_HTTP_OK,
+ "poll-payment-refund-1",
+ GNUNET_YES),
+ /* Ordinary refund. */
+ TALER_TESTING_cmd_refund_lookup ("refund-lookup-1r",
+ merchant_url,
+ "refund-increase-1r",
+ "pay-for-refund-1r",
+ "1r",
+ MHD_HTTP_OK),
+ /* Trying to pick up refund from non existent proposal. */
+ TALER_TESTING_cmd_refund_lookup ("refund-lookup-non-existent",
+ merchant_url,
+ "refund-increase-1r",
+ "deposit-simple",
+ "non-existend-id",
+ MHD_HTTP_NOT_FOUND),
+
+ /* Test /refund on a contract that was never paid. */
+ TALER_TESTING_cmd_proposal ("create-proposal-not-to-be-paid",
+ merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"1-unpaid\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":99999999999},\
+ \"amount\":\"EUR:5.0\",\
+ \"summary\": \"useful product\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }"),
+ /* Try to increase a non paid proposal. */
+ TALER_TESTING_cmd_refund_increase ("refund-increase-unpaid-proposal",
+ merchant_url,
+ "refund test",
+ "1-unpaid",
+ "EUR:0.1",
+ "EUR:0.01",
+ MHD_HTTP_CONFLICT),
+ /* Try to increase a non existent proposal. */
+ TALER_TESTING_cmd_refund_increase ("refund-increase-unpaid-proposal",
+ merchant_url,
+ "refund test",
+ "non-existent-id",
+ "EUR:0.1",
+ "EUR:0.01",
+ MHD_HTTP_NOT_FOUND),
+ /*
+ The following block will (1) create a new
+ reserve, then (2) a proposal, then (3) pay for
+ it, and finally (4) attempt to pick up a refund
+ from it without any increasing taking place
+ in the first place.
+ *///
+ cmd_transfer_to_exchange ("create-reserve-unincreased-refund",
+ "EUR:5.01"),
+ cmd_exec_wirewatch ("wirewatch-unincreased-refund"),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "check_bank_transfer-unincreased-refund",
+ "EUR:5.01",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-unincreased-refund"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unincreased-refund",
+ "create-reserve-unincreased-refund",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("create-proposal-unincreased-refund",
+ merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"unincreased-proposal\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"amount\":\"EUR:5.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }"),
+ TALER_TESTING_cmd_pay ("pay-unincreased-proposal",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-unincreased-refund",
+ "withdraw-coin-unincreased-refund",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+ CMD_EXEC_AGGREGATOR ("run-aggregator-unincreased-refund"),
+ TALER_TESTING_cmd_check_bank_transfer (
+ "check_bank_transfer-paid-unincreased-refund",
+ EXCHANGE_URL,
+ "EUR:9.88", /* '4.98 from above', plus 4.99 from 'pay-for-refund-1r'
+ and MINUS 0.1 PLUS 0.01 (deposit fee) from 'refund-increase-1r' */
+ exchange_payto,
+ merchant_payto),
+ /* Actually try to pick up the refund from the "unincreased proposal". */
+ TALER_TESTING_cmd_refund_lookup_with_amount ("refund-lookup-unincreased",
+ merchant_url,
+ NULL,
+ "pay-unincreased-proposal",
+ "unincreased-proposal",
+ MHD_HTTP_NOT_FOUND,
+ /* If a lookup is attempted
+ * on an unincreased
+ * proposal, the backend will
+ * simply respond with a
+ * empty refunded coin "set",
+ * but the HTTP response code
+ * is 200 OK. *///
+ "EUR:0"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command tip[] = {
+ /* Test tipping. */
+ TALER_TESTING_cmd_admin_add_incoming_with_instance ("create-reserve-tip-1",
+ "EUR:20.04",
+ &bc.exchange_auth,
+ payer_payto,
+ "tip",
+ CONFIG_FILE),
+ cmd_exec_wirewatch ("wirewatch-3"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-tip-1",
+ "EUR:20.04",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-tip-1"),
+ TALER_TESTING_cmd_tip_authorize ("authorize-tip-1",
+ merchant_url_internal ("tip"),
+ EXCHANGE_URL,
+ MHD_HTTP_OK,
+ "tip 1",
+ "EUR:5.01"),
+ TALER_TESTING_cmd_tip_authorize ("authorize-tip-2",
+ merchant_url_internal ("tip"),
+ EXCHANGE_URL,
+ MHD_HTTP_OK,
+ "tip 2",
+ "EUR:5.01"),
+ /* This command tests the authorization of tip
+ * against a reserve that does not exist. This is
+ * implemented by passing a "tip instance" that
+ * specifies a reserve key that was never used to
+ * actually create a reserve. *///
+ TALER_TESTING_cmd_tip_authorize_with_ec ("authorize-tip-null",
+ merchant_url_internal ("nulltip"),
+ EXCHANGE_URL,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ "tip 2",
+ "EUR:5.01",
+ TALER_EC_TIP_QUERY_RESERVE_UNKNOWN_TO_EXCHANGE),
+ TALER_TESTING_cmd_tip_query ("query-tip-1",
+ merchant_url_internal ("tip"),
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_tip_query_with_amounts ("query-tip-2",
+ merchant_url_internal ("tip"),
+ MHD_HTTP_OK,
+ "EUR:0.0", // picked
+ "EUR:10.02", // authorized
+ "EUR:20.04"),// available
+ TALER_TESTING_cmd_tip_pickup ("pickup-tip-1",
+ merchant_url_external ("tip"),
+ MHD_HTTP_OK,
+ "authorize-tip-1",
+ pickup_amounts_1),
+ TALER_TESTING_cmd_tip_query_with_amounts ("query-tip-3",
+ merchant_url_internal ("tip"),
+ MHD_HTTP_OK,
+ "EUR:5.01", // picked
+ NULL, // authorized
+ "EUR:15.03"),// available
+ TALER_TESTING_cmd_tip_pickup ("pickup-tip-2",
+ merchant_url_external ("tip"),
+ MHD_HTTP_OK,
+ "authorize-tip-2",
+ pickup_amounts_1),
+ TALER_TESTING_cmd_tip_query_with_amounts ("query-tip-4",
+ merchant_url_internal ("tip"),
+ MHD_HTTP_OK,
+ "EUR:10.02", // pick
+ "EUR:10.02", // authorized
+ "EUR:10.02"), // available
+ TALER_TESTING_cmd_admin_add_incoming_with_instance (
+ "create-reserve-insufficient-funds",
+ "EUR:1.01",
+ &bc.exchange_auth,
+ payer_payto,
+ "dtip",
+ CONFIG_FILE),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "check_bank_transfer-insufficient-tip-funds",
+ "EUR:1.01",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-insufficient-funds"),
+ cmd_exec_wirewatch ("wirewatch-insufficient-tip-funds"),
+ TALER_TESTING_cmd_tip_authorize_with_ec (
+ "authorize-tip-3-insufficient-funds",
+ merchant_url_internal ("dtip"),
+ EXCHANGE_URL,
+ MHD_HTTP_PRECONDITION_FAILED,
+ "tip 3",
+ "EUR:2.02",
+ TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS),
+ TALER_TESTING_cmd_tip_authorize_with_ec ("authorize-tip-4-unknown-instance",
+ merchant_url_internal ("unknown"),
+ EXCHANGE_URL,
+ MHD_HTTP_NOT_FOUND,
+ "tip 4",
+ "EUR:5.01",
+ TALER_EC_INSTANCE_UNKNOWN),
+ TALER_TESTING_cmd_tip_authorize_with_ec ("authorize-tip-5-notip-instance",
+ merchant_url,
+ EXCHANGE_URL,
+ MHD_HTTP_PRECONDITION_FAILED,
+ "tip 5",
+ "EUR:5.01",
+ TALER_EC_TIP_AUTHORIZE_INSTANCE_DOES_NOT_TIP),
+ TALER_TESTING_cmd_tip_pickup_with_ec ("pickup-tip-3-too-much",
+ merchant_url_external ("tip"),
+ MHD_HTTP_CONFLICT,
+ "authorize-tip-1",
+ pickup_amounts_1,
+ TALER_EC_TIP_PICKUP_NO_FUNDS),
+ TALER_TESTING_cmd_tip_authorize_fake ("fake-tip-authorization"),
+ TALER_TESTING_cmd_tip_pickup_with_ec ("pickup-non-existent-id",
+ merchant_url_external ("tip"),
+ MHD_HTTP_NOT_FOUND,
+ "fake-tip-authorization",
+ pickup_amounts_1,
+ TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN),
+ TALER_TESTING_cmd_proposal ("create-proposal-tip-1",
+ merchant_url_internal ("tip"),
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"1-tip\", \
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":99999999999999},\
+ \"amount\":\"EUR:5.0\",\
+ \"summary\": \"useful product\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }"),
+ TALER_TESTING_cmd_pay ("deposit-tip-simple",
+ merchant_url_external ("tip"),
+ MHD_HTTP_OK,
+ "create-proposal-tip-1",
+ "pickup-tip-1",
+ "EUR:5", // amount + fee
+ "EUR:4.99", // amount - fee
+ "EUR:0.01"), // refund fee
+ CMD_EXEC_AGGREGATOR ("aggregator-tip-1"),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-tip-498c",
+ EXCHANGE_URL,
+ "EUR:4.98",
+ exchange_payto,
+ merchant_payto),
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-at-tips"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command pay_again[] = {
+ cmd_transfer_to_exchange ("create-reserve-10",
+ "EUR:10.02"),
+ cmd_exec_wirewatch ("wirewatch-10"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-10",
+ "EUR:10.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-10"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10a",
+ "create-reserve-10",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10b",
+ "create-reserve-10",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_status ("withdraw-status-10",
+ "create-reserve-10",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("create-proposal-10",
+ merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"10\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":99999999999999},\
+ \"amount\":\"EUR:10.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:10}\"} ] }"),
+ TALER_TESTING_cmd_pay ("pay-fail-partial-double-10",
+ merchant_url,
+ MHD_HTTP_CONFLICT,
+ "create-proposal-10",
+ "withdraw-coin-10a;withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+ TALER_TESTING_cmd_pay_again ("pay-again-10",
+ merchant_url,
+ "pay-fail-partial-double-10",
+ "withdraw-coin-10a;withdraw-coin-10b",
+ "EUR:0.01",
+ MHD_HTTP_OK),
+ CMD_EXEC_AGGREGATOR ("run-aggregator-10"),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-9.97-10",
+ EXCHANGE_URL,
+ "EUR:9.97",
+ exchange_payto,
+ merchant_payto),
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-10"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command pay_abort[] = {
+ cmd_transfer_to_exchange ("create-reserve-11",
+ "EUR:10.02"),
+ cmd_exec_wirewatch ("wirewatch-11"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-11",
+ "EUR:10.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-11"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11a",
+ "create-reserve-11",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11b",
+ "create-reserve-11",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_status ("withdraw-status-11",
+ "create-reserve-11",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("create-proposal-11",
+ merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"11\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":99999999999999},\
+ \"amount\":\"EUR:10.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:10}\"} ] }"),
+ TALER_TESTING_cmd_pay ("pay-fail-partial-double-11-good",
+ merchant_url,
+ MHD_HTTP_NOT_ACCEPTABLE,
+ "create-proposal-11",
+ "withdraw-coin-11a",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+ TALER_TESTING_cmd_pay ("pay-fail-partial-double-11-bad",
+ merchant_url,
+ MHD_HTTP_CONFLICT,
+ "create-proposal-11",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+ TALER_TESTING_cmd_pay_abort ("pay-abort-11",
+ merchant_url,
+ "pay-fail-partial-double-11-good",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_pay_abort_refund ("pay-abort-refund-11",
+ /* abort reference */
+ "pay-abort-11",
+ 0,
+ "EUR:5",
+ "EUR:0.01",
+ MHD_HTTP_OK),
+ CMD_EXEC_AGGREGATOR ("run-aggregator-11"),
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-11"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_config ("config",
+ merchant_url,
+ MHD_HTTP_OK),
+#if 0
+ TALER_TESTING_cmd_batch ("pay",
+ pay),
+ TALER_TESTING_cmd_batch ("double-spending",
+ double_spending),
+ TALER_TESTING_cmd_batch ("track",
+ track),
+ TALER_TESTING_cmd_history ("history-2",
+ merchant_url,
+ MHD_HTTP_OK,
+ GNUNET_TIME_absolute_add (
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ GNUNET_TIME_UNIT_MICROSECONDS),
+ /* zero results expected, there isn't any row with id
+ * bigger than 10. */
+ 0,
+ 10,
+ 10),
+ TALER_TESTING_cmd_batch ("refund",
+ refund),
+ TALER_TESTING_cmd_batch ("tip",
+ tip),
+ TALER_TESTING_cmd_batch ("pay-again",
+ pay_again),
+ TALER_TESTING_cmd_batch ("pay-abort",
+ pay_abort),
+ TALER_TESTING_cmd_history_default_start ("history-default-start",
+ merchant_url,
+ MHD_HTTP_OK,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ 5, /* Expected number of records */
+ -100), /* Delta */
+#endif
+ /**
+ * End the suite. Fixme: better to have a label for this
+ * too, as it shows a "(null)" token on logs.
+ */
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run_with_fakebank (is,
+ commands,
+ bc.exchange_auth.wire_gateway_url);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ unsigned int ret;
+ /* These environment variables get in the way... */
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+
+ GNUNET_log_setup ("test-merchant-api",
+ "DEBUG",
+ NULL);
+ if (GNUNET_OK != TALER_TESTING_prepare_fakebank (CONFIG_FILE,
+ "exchange-account-exchange",
+ &bc))
+ return 77;
+
+ payer_payto = ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME);
+ exchange_payto = ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME);
+ merchant_payto = ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME);
+
+ if (NULL ==
+ (merchant_url = TALER_TESTING_prepare_merchant (CONFIG_FILE)))
+ return 77;
+
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+
+ switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+ GNUNET_YES,
+ &ec))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ return 1;
+ case GNUNET_NO:
+ return 77;
+
+ case GNUNET_OK:
+
+ if (NULL == (merchantd =
+ TALER_TESTING_run_merchant (CONFIG_FILE,
+ merchant_url)))
+ return 1;
+
+ ret = TALER_TESTING_setup_with_exchange (&run,
+ NULL,
+ CONFIG_FILE);
+
+ GNUNET_OS_process_kill (merchantd, SIGTERM);
+ GNUNET_OS_process_wait (merchantd);
+ GNUNET_OS_process_destroy (merchantd);
+ GNUNET_free (merchant_url);
+
+ if (GNUNET_OK != ret)
+ return 1;
+ break;
+ default:
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* end of test_merchant_api.c */
diff --git a/src/testing/test_merchant_api.conf b/src/testing/test_merchant_api.conf
new file mode 100644
index 00000000..955f5c69
--- /dev/null
+++ b/src/testing/test_merchant_api.conf
@@ -0,0 +1,206 @@
+# This file is in the public domain.
+#
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_merchant_api_home/
+
+# Persistent data storage
+TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
+
+# Configuration files
+TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
+
+# Cached data, no big deal if lost
+TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
+
+[taler]
+# What currency do we use?
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[bank]
+HTTP_PORT = 8082
+
+##########################################
+# Configuration for the merchant backend #
+##########################################
+
+[merchant]
+
+# Which port do we run the backend on? (HTTP server)
+PORT = 8080
+
+# Which plugin (backend) do we use for the DB.
+DB = postgres
+
+
+# This specifies which database the postgres backend uses.
+[merchantdb-postgres]
+CONFIG = postgres:///talercheck
+
+# Sections starting with "merchant-exchange-" specify trusted exchanges
+# (by the merchant)
+[merchant-exchange-test]
+MASTER_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
+EXCHANGE_BASE_URL = http://localhost:8081/
+CURRENCY = EUR
+
+
+#######################################################
+# Configuration for the auditor for the testcase
+#######################################################
+[auditor]
+BASE_URL = http://the.auditor/
+
+
+#######################################################
+# Configuration for ??? Is this used?
+#######################################################
+
+# Auditors must be in sections "auditor-", the rest of the section
+# name could be anything.
+[auditor-ezb]
+# Informal name of the auditor. Just for the user.
+NAME = European Central Bank
+
+# URL of the auditor (especially for in the future, when the
+# auditor offers an automated issue reporting system).
+# Not really used today.
+URL = http://taler.ezb.eu/
+
+# This is the important bit: the signing key of the auditor.
+PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
+
+# Which currency is this auditor trusted for?
+CURRENCY = EUR
+
+
+###################################################
+# Configuration for the exchange for the testcase #
+###################################################
+
+[exchange_keys]
+
+# how long is one signkey valid?
+signkey_duration = 4 weeks
+
+# how long are the signatures with the signkey valid?
+legal_duration = 2 years
+
+# how long do we generate denomination and signing keys
+# ahead of time?
+lookahead_sign = 32 weeks 1 day
+
+# how long do we provide to clients denomination and signing keys
+# ahead of time?
+lookahead_provide = 4 weeks 1 day
+
+
+[exchange]
+# How to access our database
+DB = postgres
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Our public key
+MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
+
+# Base URL of the exchange.
+BASE_URL = "http://localhost:8081/"
+
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+
+[auditordb-postgres]
+CONFIG = postgres:///talercheck
+
+
+# Account of the EXCHANGE
+[exchange-account-exchange]
+# What is the exchange's bank account (with the "Taler Bank" demo system)?
+PAYTO_URI = "payto://x-taler-bank/localhost/2"
+
+# This is the response we give out for the /wire request. It provides
+# wallets with the bank information for transfers to the exchange.
+WIRE_RESPONSE = ${TALER_CONFIG_HOME}/exchange/account-2.json
+
+WIRE_GATEWAY_URL = "http://localhost:8082/2/"
+WIRE_GATEWAY_AUTH_METHOD = NONE
+
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+
+# wire fees
+[fees-x-taler-bank]
+
+# Fees for the foreseeable future...
+# If you see this after 2018, update to match the next 10 years...
+WIRE-FEE-2018 = EUR:0.01
+WIRE-FEE-2019 = EUR:0.01
+WIRE-FEE-2020 = EUR:0.01
+WIRE-FEE-2021 = EUR:0.01
+WIRE-FEE-2022 = EUR:0.01
+WIRE-FEE-2023 = EUR:0.01
+WIRE-FEE-2024 = EUR:0.01
+WIRE-FEE-2025 = EUR:0.01
+WIRE-FEE-2026 = EUR:0.01
+WIRE-FEE-2027 = EUR:0.01
+CLOSING-FEE-2018 = EUR:0.01
+CLOSING-FEE-2019 = EUR:0.01
+CLOSING-FEE-2020 = EUR:0.01
+CLOSING-FEE-2021 = EUR:0.01
+CLOSING-FEE-2022 = EUR:0.01
+CLOSING-FEE-2023 = EUR:0.01
+CLOSING-FEE-2024 = EUR:0.01
+CLOSING-FEE-2025 = EUR:0.01
+CLOSING-FEE-2026 = EUR:0.01
+CLOSING-FEE-2027 = EUR:0.01
+
+
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
diff --git a/src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json b/src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json
new file mode 100644
index 00000000..b84273e8
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json
@@ -0,0 +1,4 @@
+{
+ "payto_uri": "payto://x-taler-bank/localhost/2",
+ "master_sig": "A296BYJDP182XS8GHB362ENK4JVPQYHJ34AMNC52YKSK78NH85NA3MWCPM7XAYXWSHK0X9SH46JN84T2AYGP0YAV0E9HQXAYZGABP0R"
+} \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.config/taler/merchant/account-3.json b/src/testing/test_merchant_api_home/.config/taler/merchant/account-3.json
new file mode 100644
index 00000000..70e8bbb6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/merchant/account-3.json
@@ -0,0 +1 @@
+{"payto_uri":"payto://x-taler-bank/localhost/3","salt":"KHMDP5RV4K75TYZAAZEFSB33AQK5DECEDAQB3AYCXZHB50W83RC5C08H5CRNNG9BWQXN3RD52WCQ09A3MFJDJASGB3PRPA03V3TEGCR"} \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.config/taler/merchant/default.priv b/src/testing/test_merchant_api_home/.config/taler/merchant/default.priv
new file mode 100644
index 00000000..8299ad28
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/merchant/default.priv
@@ -0,0 +1 @@
+~~j&i4qlBM:ƾOߪ# \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.config/taler/merchant/dtip.priv b/src/testing/test_merchant_api_home/.config/taler/merchant/dtip.priv
new file mode 100644
index 00000000..ff3842bb
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/merchant/dtip.priv
@@ -0,0 +1 @@
+47%Tҟ\v}"hP!. \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.config/taler/merchant/nulltip.priv b/src/testing/test_merchant_api_home/.config/taler/merchant/nulltip.priv
new file mode 100644
index 00000000..db695e5c
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/merchant/nulltip.priv
@@ -0,0 +1,2 @@
+by~]m~r1fcT04Q
+ds \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/dtip.priv b/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/dtip.priv
new file mode 100644
index 00000000..c586db18
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/dtip.priv
Binary files differ
diff --git a/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/nulltip.priv b/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/nulltip.priv
new file mode 100644
index 00000000..edf062ea
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/nulltip.priv
@@ -0,0 +1 @@
+Bu#ĜW0*~$ROpk \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/tip.priv b/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/tip.priv
new file mode 100644
index 00000000..5ee3bce0
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/tip.priv
@@ -0,0 +1 @@
+V<gJΰXc;Ki" \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.config/taler/merchant/tip.priv b/src/testing/test_merchant_api_home/.config/taler/merchant/tip.priv
new file mode 100644
index 00000000..ba920729
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/merchant/tip.priv
@@ -0,0 +1 @@
+-ylx;b~2.l8Pĕb \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.config/taler/merchant/tor.priv b/src/testing/test_merchant_api_home/.config/taler/merchant/tor.priv
new file mode 100644
index 00000000..5d94c717
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/merchant/tor.priv
@@ -0,0 +1 @@
+d\ 2ȈaK#+Nf/FslG \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.config/taler/test.json b/src/testing/test_merchant_api_home/.config/taler/test.json
new file mode 100644
index 00000000..74cdc92b
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.config/taler/test.json
@@ -0,0 +1,8 @@
+{
+ "name": "The exchange",
+ "account_number": 3,
+ "bank_url": "http://localhost:8083/",
+ "salt": "6259MV4W9V8D2A75RSGGPKYHQRXRPQZ33EBG263JZRJ6SA5HK0RRKHV70TNA1RVRG77M57CCFVSK2B0EJN3SR8S21F0ZX2MR9DNVG50",
+ "type": "test",
+ "sig": "8C3D3J816S29AA2AJ7P9TS6W13KFNFS2RCVYJEWRBNHRRMTTRAWKY7WA1N3G54E4K3XAC2HN6JDHS42TWR5315J34JHHCKV618K221G"
+}
diff --git a/src/testing/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv b/src/testing/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv
new file mode 100644
index 00000000..c20942d6
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv
@@ -0,0 +1 @@
+k;d_U}A.w"!Gv_m"_ \ No newline at end of file
diff --git a/src/testing/test_merchant_api_home/.local/share/taler/merchant/merchant.priv b/src/testing/test_merchant_api_home/.local/share/taler/merchant/merchant.priv
new file mode 100644
index 00000000..fd6e5f7f
--- /dev/null
+++ b/src/testing/test_merchant_api_home/.local/share/taler/merchant/merchant.priv
@@ -0,0 +1 @@
+,Y%FF<R9ϳ5v\k46 \ No newline at end of file
diff --git a/src/testing/test_merchant_api_proxy_exchange.conf b/src/testing/test_merchant_api_proxy_exchange.conf
new file mode 100644
index 00000000..fb4a67c7
--- /dev/null
+++ b/src/testing/test_merchant_api_proxy_exchange.conf
@@ -0,0 +1,30 @@
+# This file is in the public domain.
+
+# Config to set up the twister between the
+# merchant and the exchange.
+
+[twister]
+# HTTP listen port for twister (the merchant
+# will transparently use this URL as the "exchange")
+HTTP_PORT = 8888
+SERVE = tcp
+
+# HTTP Destination for twister, so the real exchange
+# URL. Note: no trailing '/'!
+DESTINATION_BASE_URL = "http://localhost:8081"
+
+# Control port for TCP
+# PORT = 8889
+HOSTNAME = localhost
+ACCEPT_FROM = 127.0.0.1;
+ACCEPT_FROM6 = ::1;
+
+# Control port for UNIX
+UNIXPATH = /tmp/taler-service-twister-exchange.sock
+UNIX_MATCH_UID = NO
+UNIX_MATCH_GID = YES
+
+# Launching of twister by ARM
+# BINARY = taler-service-twister
+# AUTOSTART = NO
+# FORCESTART = NO
diff --git a/src/testing/test_merchant_api_proxy_merchant.conf b/src/testing/test_merchant_api_proxy_merchant.conf
new file mode 100644
index 00000000..9ef38a06
--- /dev/null
+++ b/src/testing/test_merchant_api_proxy_merchant.conf
@@ -0,0 +1,31 @@
+# This file is in the public domain.
+
+# Config to set up the twister between the
+# HTTP client (= the "lib" code) and the
+# merchant service.
+
+[twister]
+# HTTP listen port for twister ("lib" code will
+# transparently use this URL as the "merchant")
+HTTP_PORT = 8889
+SERVE = tcp
+
+# HTTP Destination for twister, so the real
+# merchant URL. Note: no trailing '/'!
+DESTINATION_BASE_URL = "http://localhost:8080"
+
+# Control port for TCP
+# PORT = 8889
+HOSTNAME = localhost
+ACCEPT_FROM = 127.0.0.1;
+ACCEPT_FROM6 = ::1;
+
+# Control port for UNIX
+UNIXPATH = /tmp/taler-service-twister-merchant.sock
+UNIX_MATCH_UID = NO
+UNIX_MATCH_GID = YES
+
+# Launching of twister by ARM
+# BINARY = taler-service-twister
+# AUTOSTART = NO
+# FORCESTART = NO
diff --git a/src/testing/test_merchant_api_twisted.c b/src/testing/test_merchant_api_twisted.c
new file mode 100644
index 00000000..e49e2380
--- /dev/null
+++ b/src/testing/test_merchant_api_twisted.c
@@ -0,0 +1,950 @@
+/**
+ * This file is part of TALER
+ * Copyright (C) 2014-2018 Taler Systems SA
+ *
+ * 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.
+ *
+ * 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 TALER; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * @file exchange/test_merchant_api_twisted.c
+ * @brief testcase to test exchange's HTTP API interface
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ * @author Florian Dold <dold@taler.net>
+ */
+
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include <taler/taler_bank_service.h>
+#include <taler/taler_fakebank_lib.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_testing_lib.h"
+#include <taler/taler_twister_testing_lib.h>
+#include <taler/taler_twister_service.h>
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_merchant_api_twisted.conf"
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NAME "2"
+
+/**
+ * Account number of the merchant at the bank.
+ */
+#define MERCHANT_ACCOUNT_NAME "3"
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NAME "62"
+
+
+/**
+ * Configuration file for the proxy between merchant and
+ * exchange. Not used directly here in the code (instead
+ * used in the merchant config), but kept around for consistency.
+ */
+#define PROXY_EXCHANGE_CONFIG_FILE \
+ "test_merchant_api_proxy_exchange.conf"
+
+/**
+ * Configuration file for the proxy between "lib" and merchant.
+ */
+#define PROXY_MERCHANT_CONFIG_FILE \
+ "test_merchant_api_proxy_merchant.conf"
+
+/**
+ * Exchange base URL. Could also be taken from config.
+ */
+#define EXCHANGE_URL "http://localhost:8081/"
+
+/**
+ * Twister URL that proxies the exchange.
+ */
+static char *twister_exchange_url;
+
+/**
+ * Twister URL that proxies the merchant.
+ */
+static char *twister_merchant_url;
+
+/**
+ * Twister URL that proxies the merchant.
+ */
+static char *twister_merchant_url_instance_nonexistent;
+
+/**
+ * Twister URL that proxies the merchant.
+ */
+static char *twister_merchant_url_instance_tor;
+
+/**
+ * Merchant base URL.
+ */
+static char *merchant_url;
+
+/**
+ * Merchant process.
+ */
+static struct GNUNET_OS_Process *merchantd;
+
+/**
+ * Twister process that proxies the exchange.
+ */
+static struct GNUNET_OS_Process *twisterexchanged;
+
+/**
+ * Twister process that proxies the merchant.
+ */
+static struct GNUNET_OS_Process *twistermerchantd;
+
+
+static char *payer_payto;
+static char *exchange_payto;
+static char *merchant_payto;
+static struct TALER_TESTING_BankConfiguration bc;
+static struct TALER_TESTING_ExchangeConfiguration ec;
+
+/**
+ * User name. Never checked by fakebank.
+ */
+#define USER_LOGIN_NAME "user42"
+
+/**
+ * User password. Never checked by fakebank.
+ */
+#define USER_LOGIN_PASS "pass42"
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+static struct TALER_TESTING_Command
+CMD_EXEC_WIREWATCH (const char *label)
+{
+ return TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE);
+}
+
+
+/**
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_exec_aggregator (label "-aggregator", CONFIG_FILE), \
+ TALER_TESTING_cmd_exec_transfer (label "-transfer", CONFIG_FILE)
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ * @param url exchange_url
+ */
+static struct TALER_TESTING_Command
+CMD_TRANSFER_TO_EXCHANGE (const char *label,
+ const char *amount)
+{
+ return TALER_TESTING_cmd_admin_add_incoming (label,
+ amount,
+ &bc.exchange_auth,
+ payer_payto);
+}
+
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ /**** Triggering #5719 ****/
+ struct TALER_TESTING_Command bug_5719[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("5719-create-reserve",
+ "EUR:1.01"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("5719-wirewatch"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("5719-check-transfer",
+ "EUR:1.01",
+ payer_payto,
+ exchange_payto,
+ "5719-create-reserve"),
+ TALER_TESTING_cmd_withdraw_amount ("5719-withdraw",
+ "5719-create-reserve",
+ "EUR:1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_status ("5719-reserve-status",
+ "5719-create-reserve",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("5719-create-proposal",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"5719TRIGGER\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"amount\":\"EUR:1.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"triggering bug 5719\",\
+ \"value\":\"{EUR:1}\"} ] }"),
+
+ /**
+ * Instruct the Twister to malform the response given by
+ * the exchange to the merchant. This way, the parser will
+ * not manage to pass the callback a valid JSON and will
+ * instead pass a NULL pointer. This should trigger the path
+ * mentioned in the bug report #5719.
+ *///
+ TALER_TESTING_cmd_malform_response ("5719-malform-exchange-resp",
+ PROXY_EXCHANGE_CONFIG_FILE),
+ TALER_TESTING_cmd_pay ("5719-deposit",
+ twister_merchant_url,
+ MHD_HTTP_FAILED_DEPENDENCY,
+ "5719-create-proposal",
+ "5719-withdraw",
+ "EUR:1",
+ "EUR:1.99", // no sense now
+ "EUR:0.01"), // no sense now
+ TALER_TESTING_cmd_end ()
+ };
+
+
+ /**** Covering /check-payment ****/
+ struct TALER_TESTING_Command check_payment[] = {
+
+ TALER_TESTING_cmd_proposal
+ ("proposal-for-check-payment",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"fail-check-payment-1\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"amount\":\"EUR:2.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"EUR:3\"} ] }"),
+
+ /* Need any response code != 200. */
+ TALER_TESTING_cmd_hack_response_code ("non-200-response-code",
+ PROXY_MERCHANT_CONFIG_FILE,
+ MHD_HTTP_MULTIPLE_CHOICES),
+ TALER_TESTING_cmd_check_payment ("check-payment-fail",
+ twister_merchant_url,
+ MHD_HTTP_MULTIPLE_CHOICES,
+ "proposal-for-check-payment",
+ GNUNET_SYSERR), // any response != 200 gives "syserr"
+ TALER_TESTING_cmd_delete_object ("hack-check-payment-0",
+ PROXY_MERCHANT_CONFIG_FILE,
+ "taler_pay_uri"),
+ TALER_TESTING_cmd_check_payment ("check-payment-fail-invalid",
+ twister_merchant_url,
+ 0,
+ "proposal-for-check-payment",
+ GNUNET_SYSERR),
+ TALER_TESTING_cmd_modify_object_dl ("paid-true-for-unpaid",
+ PROXY_MERCHANT_CONFIG_FILE,
+ "paid",
+ "true"),
+ TALER_TESTING_cmd_check_payment ("check-payment-fail-invalid-0",
+ twister_merchant_url,
+ 0,
+ "proposal-for-check-payment",
+ GNUNET_SYSERR),
+ TALER_TESTING_cmd_end ()
+ };
+
+ /**** Covering /proposal lib ****/
+ struct TALER_TESTING_Command proposal[] = {
+
+ /**
+ * Make the merchant return a 400 Bad Request response
+ * due to uploaded body malformation.
+ */
+ TALER_TESTING_cmd_malform_request ("malform-order",
+ PROXY_MERCHANT_CONFIG_FILE),
+ TALER_TESTING_cmd_proposal ("create-proposal-0",
+ twister_merchant_url,
+ MHD_HTTP_BAD_REQUEST,
+ /* giving a valid JSON to not make it fail before
+ * data reaches the merchant. */
+ "{\"not\": \"used\"}"),
+ TALER_TESTING_cmd_hack_response_code ("proposal-500",
+ PROXY_MERCHANT_CONFIG_FILE,
+ MHD_HTTP_INTERNAL_SERVER_ERROR),
+ TALER_TESTING_cmd_proposal ("create-proposal-1",
+ twister_merchant_url,
+ /* This status code == 0 is gotten via a 500 Internal Server
+ * Error handed to the library. */
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ /* giving a valid JSON to not make it fail before
+ * data reaches the merchant. */
+ "{\"not\": \"used\"}"),
+
+ /**
+ * Cause the PUT /proposal callback to be called
+ * with a response code == 0. We achieve this by malforming
+ * the response body.
+ *///
+ TALER_TESTING_cmd_malform_response ("malform-proposal",
+ PROXY_MERCHANT_CONFIG_FILE),
+
+ TALER_TESTING_cmd_proposal ("create-proposal-2",
+ twister_merchant_url,
+ 0,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"1\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"amount\":\"EUR:5.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"EUR:5\"} ] }"),
+ /**
+ * Cause proposal to be invalid: this is achieved
+ * by deleting the "order_id" field of it.
+ */
+ TALER_TESTING_cmd_delete_object ("remove-order-id",
+ PROXY_MERCHANT_CONFIG_FILE,
+ "order_id"),
+ TALER_TESTING_cmd_proposal ("create-proposal-3",
+ twister_merchant_url,
+ 0,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"order_id\":\"2\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"amount\":\"EUR:5.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }"),
+ /**
+ * Cause a 404 Not Found response code,
+ * due to a non existing merchant instance.
+ */
+ TALER_TESTING_cmd_proposal ("create-proposal-4",
+ twister_merchant_url_instance_nonexistent,
+ MHD_HTTP_NOT_FOUND,
+ "{\"amount\":\"EUR:5\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"summary\": \"merchant-lib testcase\"}"),
+
+ /* Cause a 404 Not Found from /proposal/lookup,
+ * due to a non existing order id being queried. */
+ TALER_TESTING_cmd_proposal_lookup ("lookup-0",
+ twister_merchant_url,
+ MHD_HTTP_NOT_FOUND,
+ NULL,
+ "does-not-exist"),
+ /* Cause a unparsable response to be returned. */
+ TALER_TESTING_cmd_malform_response
+ ("malform-proposal-lookup",
+ PROXY_MERCHANT_CONFIG_FILE),
+ /* To be short, we'll make a _error_ response to be
+ * unparsable. */
+ TALER_TESTING_cmd_proposal_lookup ("lookup-1",
+ twister_merchant_url,
+ 0, // response code.
+ NULL,
+ "does-not-exist"),
+
+ /* Generating a proposal-lookup response which doesn't pass
+ * validation, by removing a field that is expected by the
+ * library. The library will call the callback with a status
+ * code of 0. */
+
+ /* First step is to create a _valid_ proposal, so that
+ * we can lookup for it later. */
+ TALER_TESTING_cmd_proposal ("create-proposal-5",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"5\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"amount\":\"EUR:5.0\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }"),
+
+ /* Remove expected field. */
+ TALER_TESTING_cmd_delete_object ("remove-contract-terms",
+ PROXY_MERCHANT_CONFIG_FILE,
+ "contract_terms"),
+
+ /* lookup! */
+ TALER_TESTING_cmd_proposal_lookup ("lookup-5",
+ twister_merchant_url,
+ // expected response code.
+ 0,
+ "create-proposal-5",
+ NULL),
+ TALER_TESTING_cmd_end ()
+ };
+
+ /**** Covering /history lib ****/
+ struct TALER_TESTING_Command history[] = {
+
+ /**
+ * Changing the response code to a unexpected
+ * one. NOTE: this is unexpected to the *lib*
+ * code, that is then expected to trigger some
+ * emergency behaviour, like setting the response
+ * code to zero before calling the callback.
+ *///
+ TALER_TESTING_cmd_hack_response_code ("twist-history",
+ PROXY_MERCHANT_CONFIG_FILE,
+ MHD_HTTP_GONE),
+ TALER_TESTING_cmd_history ("history-0",
+ twister_merchant_url,
+ MHD_HTTP_GONE,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ 1, // nresult
+ 10, // start
+ 10), // nrows
+ /**
+ * Making the returned response malformed, in order
+ * to make the JSON downloader+parser fail and call
+ * the lib passing a response code as zero.
+ *///
+ TALER_TESTING_cmd_malform_response ("malform-history",
+ PROXY_MERCHANT_CONFIG_FILE),
+
+ TALER_TESTING_cmd_history ("history-1",
+ twister_merchant_url,
+ 0, // also works with MHD_HTTP_GONE
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ 1, // nresult
+ 10, // start
+ 10), // nrows
+
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ /**
+ * This block tests whether a refund_deadline and/or
+ * wire_transfer_deadline very far in the future do NOT
+ * result in any wire transfer from the aggregator (#5366).
+ *///
+ struct TALER_TESTING_Command unaggregation[] = {
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregation",
+ "EUR:5.01"),
+ CMD_EXEC_WIREWATCH ("wirewatch-unaggregation"),
+ TALER_TESTING_cmd_check_bank_admin_transfer (
+ "check_bank_transfer-unaggregation",
+ "EUR:5.01",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-unaggregation"),
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_unaggregated-a"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregation",
+ "create-reserve-unaggregation",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("create-proposal-unaggregation",
+ /* Need a fresh instance in order to associate this
+ * proposal with a fresh h_wire; this way, this proposal
+ * won't get hooked by the aggregator gathering same-h_wire'd
+ * transactions. */
+ twister_merchant_url_instance_tor,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"refund_deadline\":{\"t_ms\":2000},\
+ \"pay_deadline\":{\"t_ms\":2366841500000},\
+ \"wire_transfer_deadline\":{\"t_ms\":2366841600000},\
+ \"amount\":\"EUR:0.5\",\
+ \"summary\": \"unaggregated product\",\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"products\": [ {\"description\":\"unaggregated cream\",\
+ \"value\":\"{EUR:5}\"} ] }"),
+ TALER_TESTING_cmd_pay ("pay-unaggregation",
+ twister_merchant_url_instance_tor,
+ MHD_HTTP_OK,
+ "create-proposal-unaggregation",
+ "withdraw-coin-unaggregation",
+ "EUR:5", // amount + fee
+ "EUR:4.99", // amount - fee
+ "EUR:0.01"), // refund fee
+ CMD_EXEC_AGGREGATOR ("aggregation-attempt"),
+ /* Make sure NO aggregation took place. */
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_unaggregated-b"),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ /***** Test #5383 *****/
+ struct TALER_TESTING_Command track_5383[] = {
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-5383",
+ "EUR:2.02"),
+ CMD_EXEC_WIREWATCH ("wirewatch-5383"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-5383",
+ "EUR:2.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-5383"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-5383a",
+ "create-reserve-5383",
+ "EUR:1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-5383b",
+ "create-reserve-5383",
+ "EUR:1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("create-proposal-5383",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"5383\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"amount\":\"EUR:2.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:2}\"} ] }"),
+ TALER_TESTING_cmd_pay ("deposit-simple-5383",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-5383",
+ "withdraw-coin-5383a;" \
+ "withdraw-coin-5383b",
+ "EUR:2",
+ "EUR:1.99", // no sense now
+ "EUR:0.01"), // no sense now
+ CMD_EXEC_AGGREGATOR ("run-aggregator-5383"),
+ TALER_TESTING_cmd_check_bank_transfer ("check_aggregation_transfer-5383",
+ twister_exchange_url,
+ /* paid, 1.97 =
+ brutto 2.00 -
+ deposit fee 0.01 * 2 -
+ wire fee 0.01
+ */"EUR:1.97",
+ exchange_payto,
+ merchant_payto),
+ TALER_TESTING_cmd_modify_object_dl ("hack-5383",
+ PROXY_EXCHANGE_CONFIG_FILE,
+ "total",
+ "EUR:0.98"),
+ TALER_TESTING_cmd_merchant_track_transfer ("track-5383",
+ twister_merchant_url,
+ MHD_HTTP_FAILED_DEPENDENCY,
+ "check_aggregation_transfer-5383"),
+ TALER_TESTING_cmd_end ()
+ };
+
+
+ /***** Test transactions tracking *****/
+ struct TALER_TESTING_Command track[] = {
+
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
+ "EUR:2.02"),
+
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-1"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2",
+ "EUR:2.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-1"),
+ TALER_TESTING_cmd_check_bank_empty ("track_chunk_check_empty-a"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-1",
+ "EUR:1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-1",
+ "EUR:1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_status ("withdraw-status",
+ "create-reserve-1",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("create-proposal-6",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"11\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"amount\":\"EUR:2.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:3}\"} ] }"),
+
+ TALER_TESTING_cmd_check_payment ("check-payment-1",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-6",
+ GNUNET_NO),
+ TALER_TESTING_cmd_pay ("deposit-simple",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-6",
+ "withdraw-coin-1;" \
+ "withdraw-coin-2",
+ "EUR:2",
+ "EUR:1.99", // no sense now
+ "EUR:0.01"), // no sense now
+ TALER_TESTING_cmd_check_payment ("check-payment-2",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-6",
+ GNUNET_YES),
+ CMD_EXEC_AGGREGATOR ("run-aggregator"),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-1",
+ twister_exchange_url, /* has the 8888-port thing. */
+ /* paid, 1.97 =
+ brutto 2.00 -
+ deposit fee 0.01 * 2 -
+ wire fee 0.01
+ *///
+ "EUR:1.97",
+ exchange_payto,
+ merchant_payto),
+
+ /**
+ * Fake total to include only one coin. Math: each 1-EUR
+ * coin contributes 0.99 to the final wire transfer. The
+ * wire transfer itself drains 0.01-EUR from the total amount.
+ * Therefore, wire transferring 1-EUR coin results in a net
+ * of: 0.99 - 0.01 = 0.98. *//**
+ * NOTE: the following two hacks aim at modifying the
+ * communication between the merchant and the exchange.
+ * In particular, they are supposed to modify the call
+ * to /track/transfer issued from the merchant to the
+ * exchange that happens _before_ the call to /track/transaction
+ * issued below by the test case (to the merchant backend.) *///
+ TALER_TESTING_cmd_modify_object_dl ("hack-0",
+ PROXY_EXCHANGE_CONFIG_FILE,
+ "total",
+ "EUR:0.98"),
+ TALER_TESTING_cmd_delete_object ("hack-1",
+ PROXY_EXCHANGE_CONFIG_FILE,
+ "deposits.0"),
+ TALER_TESTING_cmd_merchant_track_transaction ("track-transaction-1",
+ twister_merchant_url,
+ MHD_HTTP_FAILED_DEPENDENCY,
+ "deposit-simple"),
+ TALER_TESTING_cmd_end ()
+ };
+
+
+ /****** Covering /pay *******/
+ struct TALER_TESTING_Command pay[] = {
+
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-abort-1",
+ "EUR:1.01"),
+
+ /**
+ * Make a reserve exist, according to the previous transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-abort-1"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-abort-1",
+ "EUR:1.01",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-abort-1"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-abort-1",
+ "create-reserve-abort-1",
+ "EUR:1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_status ("withdraw-status-abort-1",
+ "create-reserve-abort-1",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_proposal ("create-proposal-abort-1",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"abort-one\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"amount\":\"EUR:3.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:3}\"} ] }"),
+ /* Will only pay _half_ the supposed price,
+ * so we'll then have the right to abort. */
+ TALER_TESTING_cmd_pay ("deposit-simple-for-abort",
+ twister_merchant_url,
+ MHD_HTTP_NOT_ACCEPTABLE,
+ "create-proposal-abort-1",
+ "withdraw-coin-abort-1",
+ "EUR:1",
+ "EUR:1.99", // no sense now
+ "EUR:0.01"), // no sense now
+ TALER_TESTING_cmd_delete_object ("hack-abort-1",
+ PROXY_MERCHANT_CONFIG_FILE,
+ "merchant_pub"),
+ TALER_TESTING_cmd_pay_abort ("pay-abort-1",
+ twister_merchant_url,
+ "deposit-simple-for-abort",
+ 0),
+ TALER_TESTING_cmd_delete_object ("hack-abort-2",
+ PROXY_MERCHANT_CONFIG_FILE,
+ "refund_permissions.0.rtransaction_id"),
+ TALER_TESTING_cmd_pay_abort ("pay-abort-2",
+ twister_merchant_url,
+ "deposit-simple-for-abort",
+ 0),
+ TALER_TESTING_cmd_modify_object_dl ("hack-abort-3",
+ PROXY_MERCHANT_CONFIG_FILE,
+ "refund_permissions.0.coin_pub",
+ /* dummy coin. */
+ "8YX10E41ZWHX0X2RK4XFAXB2D3M05M1HNG14ZFZZB8M7SA4QCKCG"),
+ TALER_TESTING_cmd_pay_abort ("pay-abort-3",
+ twister_merchant_url,
+ "deposit-simple-for-abort",
+ 0),
+ TALER_TESTING_cmd_flip_download ("hack-abort-4",
+ PROXY_MERCHANT_CONFIG_FILE,
+ "refund_permissions.0.merchant_sig"),
+ TALER_TESTING_cmd_pay_abort ("pay-abort-4",
+ twister_merchant_url,
+ "deposit-simple-for-abort",
+ 0),
+ /* just malforming the response. */
+ TALER_TESTING_cmd_malform_response ("malform-abortion",
+ PROXY_MERCHANT_CONFIG_FILE),
+ TALER_TESTING_cmd_pay_abort ("pay-abort-5",
+ twister_merchant_url,
+ "deposit-simple-for-abort",
+ 0),
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-double-spend",
+ "EUR:1.01"),
+ CMD_EXEC_WIREWATCH ("wirewatch-double-spend"),
+ TALER_TESTING_cmd_proposal ("create-proposal-double-spend",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"DS-1\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"amount\":\"EUR:1.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\": \"will succeed\"}] }"),
+ TALER_TESTING_cmd_proposal ("create-proposal-double-spend-1",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\"EUR:0.5\",\
+ \"order_id\":\"DS-2\",\
+ \"refund_deadline\":{\"t_ms\":0},\
+ \"pay_deadline\":{\"t_ms\":\"never\"},\
+ \"fulfillment_url\": \"https://example.com/\",\
+ \"amount\":\"EUR:1.0\",\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\": \"will fail\"}] }"),
+
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-double-spend",
+ "create-reserve-double-spend",
+ "EUR:1",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_pay ("deposit-simple-ok",
+ twister_merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-double-spend",
+ "withdraw-coin-double-spend",
+ "EUR:1",
+ "EUR:1.99", // no sense now
+ "EUR:0.01"), // no sense now
+ TALER_TESTING_cmd_flip_download ("hack-coin-history",
+ PROXY_MERCHANT_CONFIG_FILE,
+ "history.0.coin_sig"),
+ /* Coin history check will fail,
+ * due to coin's bad signature. */
+ TALER_TESTING_cmd_pay ("deposit-simple-fail",
+ twister_merchant_url,
+ MHD_HTTP_CONFLICT,
+ "create-proposal-double-spend-1",
+ "withdraw-coin-double-spend",
+ "EUR:1",
+ "EUR:1.99", // no sense now
+ "EUR:0.01"), // no sense now
+ /* max uint64 number: 9223372036854775807; try to overflow! */
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+ TALER_TESTING_cmd_batch ("check-payment",
+ check_payment),
+ TALER_TESTING_cmd_batch ("proposal",
+ proposal),
+ TALER_TESTING_cmd_batch ("history",
+ history),
+ TALER_TESTING_cmd_batch ("unaggregation",
+ unaggregation),
+ TALER_TESTING_cmd_batch ("track",
+ track),
+ TALER_TESTING_cmd_batch ("track-5383",
+ track_5383),
+ TALER_TESTING_cmd_batch ("pay",
+ pay),
+ TALER_TESTING_cmd_batch ("bug-5719",
+ bug_5719),
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run_with_fakebank (is,
+ commands,
+ bc.exchange_auth.wire_gateway_url);
+}
+
+
+/**
+ * Kill, wait, and destroy convenience function.
+ *
+ * @param process process to purge.
+ */
+static void
+purge_process (struct GNUNET_OS_Process *process)
+{
+ GNUNET_OS_process_kill (process, SIGINT);
+ GNUNET_OS_process_wait (process);
+ GNUNET_OS_process_destroy (process);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ unsigned int ret;
+ /* These environment variables get in the way... */
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-merchant-api-twisted",
+ "DEBUG", NULL);
+
+ if (GNUNET_OK != TALER_TESTING_prepare_fakebank (CONFIG_FILE,
+ "exchange-account-exchange",
+ &bc))
+ return 77;
+
+
+ payer_payto = ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME);
+ exchange_payto = ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME);
+ merchant_payto = ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME);
+
+ if (NULL == (merchant_url = TALER_TESTING_prepare_merchant
+ (CONFIG_FILE)))
+ return 77;
+
+ if (NULL == (twister_exchange_url = TALER_TWISTER_prepare_twister
+ (PROXY_EXCHANGE_CONFIG_FILE)))
+ return 77;
+
+ if (NULL == (twister_merchant_url = TALER_TWISTER_prepare_twister
+ (PROXY_MERCHANT_CONFIG_FILE)))
+ return 77;
+
+ twister_merchant_url_instance_nonexistent = TALER_url_join (
+ twister_merchant_url, "instances/foo/", NULL);
+ twister_merchant_url_instance_tor = TALER_url_join (
+ twister_merchant_url, "instances/tor/", NULL);
+
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+
+ switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+ GNUNET_YES,
+ &ec))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ return 1;
+ case GNUNET_NO:
+ return 77;
+
+ case GNUNET_OK:
+
+ if (NULL == (merchantd = TALER_TESTING_run_merchant
+ (CONFIG_FILE, merchant_url)))
+ // 1 is fine; after all this is merchant test cases.
+ return 1;
+
+ if (NULL == (twisterexchanged = TALER_TWISTER_run_twister
+ (PROXY_EXCHANGE_CONFIG_FILE)))
+ return 77;
+
+ if (NULL == (twistermerchantd = TALER_TWISTER_run_twister
+ (PROXY_MERCHANT_CONFIG_FILE)))
+ return 77;
+
+ /* Run the exchange and schedule 'run()' */
+ ret = TALER_TESTING_setup_with_exchange (&run, NULL,
+ CONFIG_FILE);
+ purge_process (merchantd);
+ purge_process (twisterexchanged);
+ purge_process (twistermerchantd);
+ GNUNET_free (merchant_url);
+ GNUNET_free (twister_exchange_url);
+ GNUNET_free (twister_merchant_url);
+
+ if (GNUNET_OK != ret)
+ return 1;
+ break;
+ default:
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* end of test_merchant_api_twisted.c */
diff --git a/src/testing/test_merchant_api_twisted.conf b/src/testing/test_merchant_api_twisted.conf
new file mode 100644
index 00000000..5cc28b48
--- /dev/null
+++ b/src/testing/test_merchant_api_twisted.conf
@@ -0,0 +1,18 @@
+# This file is in the public domain.
+@INLINE@ test_merchant_api.conf
+
+[merchant-exchange-test]
+# must target the twister's http port.
+EXCHANGE_BASE_URL = http://localhost:8888/
+
+[exchange]
+BASE_URL = http://localhost:8888/
+
+[auditor]
+BASE_URL = http://the.auditor/
+
+# merchant: 8080
+# exchange: 8081
+# (Fake)bank: 8082
+# twisted-exchange: 8888
+# twisted-merchant: 8889
diff --git a/src/testing/testing_api_cmd_check_payment.c b/src/testing/testing_api_cmd_check_payment.c
new file mode 100644
index 00000000..8d59129e
--- /dev/null
+++ b/src/testing/testing_api_cmd_check_payment.c
@@ -0,0 +1,495 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2019 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_cmd_check_payment.c
+ * @brief command to test the /check-payment feature.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_signatures.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a /check-payment conclude CMD.
+ */
+struct CheckPaymentConcludeState;
+
+/**
+ * State for a /check-payment CMD.
+ */
+struct CheckPaymentState
+{
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_MERCHANT_CheckPaymentOperation *cpo;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * The merchant base URL.
+ */
+ const char *merchant_url;
+
+ /**
+ * Reference to a command that can provide a order id,
+ * typically a /proposal test command.
+ */
+ const char *proposal_reference;
+
+ /**
+ * State for a /check-payment conclude CMD.
+ */
+ struct CheckPaymentConcludeState *cs;
+
+ /**
+ * 0 if long-polling is not desired.
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * Set to the start time of the @e cpo plus the @e timeout.
+ */
+ struct GNUNET_TIME_Absolute deadline;
+
+ /**
+ * #GNUNET_YES if we expect the proposal was paid, synchronous variant.
+ */
+ int expect_paid;
+
+ /**
+ * #GNUNET_YES if the proposal was paid.
+ */
+ int paid;
+
+ /**
+ * #GNUNET_YES if the proposal was paid and then refunded
+ */
+ int refunded;
+
+ /**
+ * Observed HTTP response status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Expected HTTP response status code, synchronous variant.
+ */
+ unsigned int expected_http_status;
+
+};
+
+
+/**
+ * State for a /check-payment conclude CMD.
+ */
+struct CheckPaymentConcludeState
+{
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to a command that can provide a check payment start command.
+ */
+ const char *start_reference;
+
+ /**
+ * Task to wait for the deadline.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Expected HTTP response status code.
+ */
+ unsigned int expected_http_status;
+
+ /**
+ * #GNUNET_YES if the proposal was expected to be paid.
+ */
+ int expected_paid;
+
+};
+
+
+/**
+ * Free a /check-payment CMD, and possibly cancel a pending
+ * operation thereof.
+ *
+ * @param cls closure
+ * @param cmd the command currently getting freed.
+ */
+static void
+check_payment_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct CheckPaymentState *cps = cls;
+
+ if (NULL != cps->cpo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command `%s' was not terminated\n",
+ TALER_TESTING_interpreter_get_current_label (
+ cps->is));
+ TALER_MERCHANT_check_payment_cancel (cps->cpo);
+ }
+ GNUNET_free (cps);
+}
+
+
+/**
+ * Task called when either the timeout for the /check-payment
+ * command expired or we got a response. Checks if the
+ * result is what we expected.
+ *
+ * @param cls a `struct CheckPaymentConcludeState`
+ */
+static void
+conclude_task (void *cls)
+{
+ struct CheckPaymentConcludeState *cpc = cls;
+ const struct TALER_TESTING_Command *check_cmd;
+ struct CheckPaymentState *cps;
+ struct GNUNET_TIME_Absolute now;
+
+ cpc->task = NULL;
+ check_cmd =
+ TALER_TESTING_interpreter_lookup_command (cpc->is,
+ cpc->start_reference);
+ if (NULL == check_cmd)
+ TALER_TESTING_FAIL (cpc->is);
+ cps = check_cmd->cls;
+ if (NULL != cps->cpo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected /poll/payment to have completed, but it did not!\n");
+ TALER_TESTING_FAIL (cpc->is);
+ }
+ if (cps->http_status != cpc->expected_http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected HTTP status %u, got %u\n",
+ cpc->expected_http_status,
+ cps->http_status);
+ TALER_TESTING_FAIL (cps->is);
+ }
+ now = GNUNET_TIME_absolute_get ();
+ if ( (GNUNET_NO == cps->paid) &&
+ (GNUNET_TIME_absolute_add (cps->deadline,
+ GNUNET_TIME_UNIT_SECONDS).abs_value_us <
+ now.abs_value_us) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected answer to be delayed until %llu, but got response at %llu\n",
+ (unsigned long long) cps->deadline.abs_value_us,
+ (unsigned long long) now.abs_value_us);
+ TALER_TESTING_FAIL (cps->is);
+ }
+ if (cps->paid != cpc->expected_paid)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected paid status %u, got %u\n",
+ cpc->expected_paid,
+ cps->paid);
+ TALER_TESTING_FAIL (cps->is);
+ }
+ TALER_TESTING_interpreter_next (cps->is);
+}
+
+
+/**
+ * Callback for a /check-payment request.
+ *
+ * @param cls closure.
+ * @param hr HTTP response we got
+ * @param paid #GNUNET_YES (#GNUNET_NO) if the contract was paid
+ * (not paid).
+ * @param refunded #GNUNET_YES (#GNUNET_NO) if the contract was
+ * refunded (not refunded).
+ * @param refund_amount the amount that was refunded to this
+ * contract.
+ * @param taler_pay_uri the URI that instructs the wallets to process
+ * the payment
+ */
+static void
+check_payment_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ int paid,
+ int refunded,
+ struct TALER_Amount *refund_amount,
+ const char *taler_pay_uri)
+{
+ struct CheckPaymentState *cps = cls;
+
+ cps->cpo = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "check payment (%s): expected paid: %d, paid: %d, url: %s\n",
+ TALER_TESTING_interpreter_get_current_label (cps->is),
+ cps->expect_paid,
+ paid,
+ taler_pay_uri);
+ cps->paid = paid;
+ cps->http_status = hr->http_status;
+ cps->refunded = refunded;
+ if (0 == cps->timeout.rel_value_us)
+ {
+ /* synchronous variant */
+ if (paid != cps->expect_paid)
+ TALER_TESTING_FAIL (cps->is);
+ if (cps->expected_http_status != hr->http_status)
+ TALER_TESTING_FAIL (cps->is);
+ TALER_TESTING_interpreter_next (cps->is);
+ }
+ else
+ {
+ /* asynchronous variant */
+ if (NULL != cps->cs)
+ {
+ GNUNET_SCHEDULER_cancel (cps->cs->task);
+ cps->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
+ cps->cs);
+ }
+ }
+}
+
+
+/**
+ * Run a /check-payment CMD.
+ *
+ * @param cmd the command currently being run.
+ * @param cls closure.
+ * @param is interpreter state.
+ */
+static void
+check_payment_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct CheckPaymentState *cps = cls;
+ const struct TALER_TESTING_Command *proposal_cmd;
+ const char *order_id;
+
+ cps->is = is;
+ proposal_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ cps->proposal_reference);
+ if (NULL == proposal_cmd)
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK != TALER_TESTING_get_trait_order_id (
+ proposal_cmd, 0, &order_id))
+ TALER_TESTING_FAIL (is);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Checking for order id `%s'\n",
+ order_id);
+ cps->cpo = TALER_MERCHANT_check_payment (is->ctx,
+ cps->merchant_url,
+ order_id,
+ NULL,
+ cps->timeout,
+ &check_payment_cb,
+ cps);
+ GNUNET_assert (NULL != cps->cpo);
+ if (0 != cps->timeout.rel_value_us)
+ TALER_TESTING_interpreter_next (cps->is);
+}
+
+
+/**
+ * Make a "check payment" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference the proposal whose payment status
+ * is going to be checked.
+ * @param expect_paid #GNUNET_YES if we expect the proposal to be
+ * paid, #GNUNET_NO otherwise.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_payment (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ unsigned int expect_paid)
+{
+ struct CheckPaymentState *cps;
+
+ cps = GNUNET_new (struct CheckPaymentState);
+ cps->expected_http_status = http_status;
+ cps->proposal_reference = proposal_reference;
+ cps->expect_paid = expect_paid;
+ cps->merchant_url = merchant_url;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cps,
+ .label = label,
+ .run = &check_payment_run,
+ .cleanup = &check_payment_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Make a "check payment" test command with long polling support.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param proposal_reference the proposal whose payment status
+ * is going to be checked.
+ * @param timeout how long to wait during long polling for the reply
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_payment_start (const char *label,
+ const char *merchant_url,
+ const char *proposal_reference,
+ struct GNUNET_TIME_Relative timeout)
+{
+ struct CheckPaymentState *cps;
+
+ if (0 == timeout.rel_value_us)
+ timeout.rel_value_us = 1; /* 0 reserved for blocking version */
+ cps = GNUNET_new (struct CheckPaymentState);
+ cps->timeout = timeout;
+ cps->proposal_reference = proposal_reference;
+ cps->merchant_url = merchant_url;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cps,
+ .label = label,
+ .run = &check_payment_run,
+ .cleanup = &check_payment_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Free a /check-payment conclusion CMD, and possibly cancel a pending
+ * operation thereof.
+ *
+ * @param cls closure
+ * @param cmd the command currently getting freed.
+ */
+static void
+check_payment_conclude_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct CheckPaymentConcludeState *cps = cls;
+
+ if (NULL != cps->task)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Command `%s' was not terminated\n",
+ TALER_TESTING_interpreter_get_current_label (
+ cps->is));
+ GNUNET_SCHEDULER_cancel (cps->task);
+ cps->task = NULL;
+ }
+ GNUNET_free (cps);
+}
+
+
+/**
+ * Run a /check-payment conclusion CMD.
+ *
+ * @param cmd the command currently being run.
+ * @param cls closure.
+ * @param is interpreter state.
+ */
+static void
+check_payment_conclude_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct CheckPaymentConcludeState *cpc = cls;
+ const struct TALER_TESTING_Command *check_cmd;
+ struct CheckPaymentState *cps;
+
+ cpc->is = is;
+ check_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ cpc->start_reference);
+ if (NULL == check_cmd)
+ TALER_TESTING_FAIL (cpc->is);
+ GNUNET_assert (check_cmd->run == &check_payment_run);
+ cps = check_cmd->cls;
+ if (NULL == cps->cpo)
+ cpc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
+ cpc);
+ else
+ cpc->task = GNUNET_SCHEDULER_add_at (cps->deadline,
+ &conclude_task,
+ cpc);
+}
+
+
+/**
+ * Expect completion of a long-polled "check payment" test command.
+ *
+ * @param label command label.
+ * @param check_start_reference payment start operation that should have
+ * completed
+ * @param http_status expected HTTP response code.
+ * @param expect_paid #GNUNET_YES if we expect the proposal to be
+ * paid, #GNUNET_NO otherwise.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_payment_conclude (const char *label,
+ unsigned int http_status,
+ const char *poll_start_reference,
+ unsigned int expect_paid)
+{
+ struct CheckPaymentConcludeState *cps;
+
+ cps = GNUNET_new (struct CheckPaymentConcludeState);
+ cps->start_reference = poll_start_reference;
+ cps->expected_paid = expect_paid;
+ cps->expected_http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cps,
+ .label = label,
+ .run = &check_payment_conclude_run,
+ .cleanup = &check_payment_conclude_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_check_payment.c */
diff --git a/src/testing/testing_api_cmd_config.c b/src/testing/testing_api_cmd_config.c
new file mode 100644
index 00000000..3e638697
--- /dev/null
+++ b/src/testing/testing_api_cmd_config.c
@@ -0,0 +1,164 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_config.c
+ * @brief command to test config request
+ * @author Christian Grothoff
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a "config" CMD.
+ */
+struct ConfigState
+{
+ /**
+ * Operation handle for a GET /public/config request.
+ */
+ struct TALER_MERCHANT_ConfigGetHandle *vgh;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_code;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+};
+
+
+/**
+ * Free the state of a "config" CMD, and
+ * possibly cancel a pending "config" operation.
+ *
+ * @param cls closure with the `struct ConfigState`
+ * @param cmd command currently being freed.
+ */
+static void
+config_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ConfigState *cs = cls;
+
+ if (NULL != cs->vgh)
+ {
+ TALER_LOG_WARNING ("config operation did not complete\n");
+ TALER_MERCHANT_config_get_cancel (cs->vgh);
+ }
+ GNUNET_free (cs);
+}
+
+
+/**
+ * Process "GET /public/config" (lookup) response.
+ *
+ * @param cls closure
+ * @param hr HTTP response we got
+ * @param ci basic information about the merchant
+ * @param compat protocol compatibility information
+ */
+static void
+config_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_MERCHANT_ConfigInformation *ci,
+ enum TALER_MERCHANT_VersionCompatibility compat)
+{
+ struct ConfigState *cs = cls;
+
+ (void) ci;
+ cs->vgh = NULL;
+ if (cs->http_code != hr->http_status)
+ TALER_TESTING_FAIL (cs->is);
+ if (TALER_MERCHANT_VC_MATCH != compat)
+ TALER_TESTING_FAIL (cs->is);
+ TALER_TESTING_interpreter_next (cs->is);
+}
+
+
+/**
+ * Run the "config" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being currently run.
+ * @param is interpreter state.
+ */
+static void
+config_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ConfigState *cs = cls;
+
+ cs->is = is;
+ cs->vgh = TALER_MERCHANT_config_get (is->ctx,
+ cs->merchant_url,
+ &config_cb,
+ cs);
+ GNUNET_assert (NULL != cs->vgh);
+}
+
+
+/**
+ * Define a "config" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * "config" request.
+ * @param http_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_config (const char *label,
+ const char *merchant_url,
+ unsigned int http_code)
+{
+ struct ConfigState *cs;
+
+ cs = GNUNET_new (struct ConfigState);
+ cs->merchant_url = merchant_url;
+ cs->http_code = http_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cs,
+ .label = label,
+ .run = &config_run,
+ .cleanup = &config_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_config.c */
diff --git a/src/testing/testing_api_cmd_history.c b/src/testing/testing_api_cmd_history.c
new file mode 100644
index 00000000..dabbf3cc
--- /dev/null
+++ b/src/testing/testing_api_cmd_history.c
@@ -0,0 +1,359 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_history.c
+ * @brief command to test the /history API.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a "history" CMD.
+ */
+struct HistoryState
+{
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * URL of the merchant backend serving the /history request.
+ */
+ const char *merchant_url;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Handle to the /history operation.
+ */
+ struct TALER_MERCHANT_HistoryOperation *ho;
+
+ /**
+ * Only history entries younger than this
+ * value will be returned.
+ */
+ struct GNUNET_TIME_Absolute time;
+
+ /**
+ * First row index we want in the results.
+ */
+ unsigned long long start;
+
+ /**
+ * When this flag is GNUNET_YES, then the interpreter
+ * will request /history *omitting* the 'start' URL argument.
+ */
+ int use_default_start;
+
+ /**
+ * How many rows we want the response to contain, at most.
+ */
+ long long nrows;
+
+ /**
+ * Expected number of history entries returned by the
+ * backend.
+ */
+ unsigned int nresult;
+};
+
+
+/**
+ * Callback for a /history request; checks that (1) HTTP status
+ * is expected, the number of rows returned is expected, and that
+ * the rows are sorted from the youngest to the oldest record.
+ *
+ * @param cls closure
+ * @param hr HTTP response we got
+ */
+static void
+history_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct HistoryState *hs = cls;
+ unsigned int nresult;
+ struct GNUNET_TIME_Absolute last_timestamp;
+ struct GNUNET_TIME_Absolute entry_timestamp;
+ json_t *arr;
+
+ hs->ho = NULL;
+
+ if (hs->http_status != hr->http_status)
+ TALER_TESTING_FAIL (hs->is);
+
+ if (MHD_HTTP_OK != hs->http_status)
+ {
+ /* move on without further checking. */
+ TALER_TESTING_interpreter_next (hs->is);
+ return;
+ }
+
+ arr = json_object_get (hr->reply,
+ "history");
+ nresult = json_array_size (arr);
+ if (hs->nresult != nresult)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected number of history entries: Got %d, expected %d\n",
+ nresult,
+ hs->nresult);
+ TALER_TESTING_FAIL (hs->is);
+ }
+
+ last_timestamp = GNUNET_TIME_absolute_get ();
+ last_timestamp = GNUNET_TIME_absolute_add (last_timestamp,
+ GNUNET_TIME_UNIT_DAYS);
+ {
+ json_t *entry;
+ size_t index;
+ json_array_foreach (arr, index, entry)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_absolute_time ("timestamp",
+ &entry_timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (entry,
+ spec,
+ NULL, NULL))
+ TALER_TESTING_FAIL (hs->is);
+ if (last_timestamp.abs_value_us < entry_timestamp.abs_value_us)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "History entries are NOT sorted from younger to older\n");
+ TALER_TESTING_interpreter_fail (hs->is);
+ return;
+ }
+ last_timestamp = entry_timestamp;
+ }
+ }
+ TALER_TESTING_interpreter_next (hs->is);
+}
+
+
+/**
+ * Free the state for a "history" CMD, and possibly cancel
+ * any pending operation thereof.
+ *
+ * @param cls closure
+ * @param cmd command being freed now.
+ */
+static void
+history_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct HistoryState *hs = cls;
+
+ if (NULL != hs->ho)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/history operation did not complete\n");
+ TALER_MERCHANT_history_cancel (hs->ho);
+ }
+ GNUNET_free (hs);
+}
+
+
+/**
+ * Run a "history" CMD.
+ *
+ * @param cls closure.
+ * @param cmd current command.
+ * @param is interpreter state.
+ */
+static void
+history_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct HistoryState *hs = cls;
+
+ hs->is = is;
+ if (0 == hs->time.abs_value_us)
+ {
+ hs->time = GNUNET_TIME_absolute_add
+ (GNUNET_TIME_absolute_get (),
+ GNUNET_TIME_UNIT_HOURS);
+ GNUNET_TIME_round_abs (&hs->time);
+ }
+
+ switch (hs->use_default_start)
+ {
+ case GNUNET_YES:
+ hs->ho = TALER_MERCHANT_history_default_start
+ (is->ctx,
+ hs->merchant_url,
+ hs->nrows,
+ hs->time,
+ &history_cb,
+ hs);
+ break;
+
+ case GNUNET_NO:
+ hs->ho = TALER_MERCHANT_history (is->ctx,
+ hs->merchant_url,
+ hs->start,
+ hs->nrows,
+ hs->time,
+ &history_cb,
+ hs);
+ break;
+ default:
+ TALER_LOG_ERROR ("Bad value for 'use_default_start'\n");
+ TALER_TESTING_FAIL (is);
+ }
+
+ if (NULL == hs->ho)
+ TALER_TESTING_FAIL (is);
+}
+
+
+/**
+ * Make a "history" command.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * request.
+ * @param ctx CURL context.
+ * @param http_status expected HTTP response code
+ * @param time limit towards the past for the history
+ * records we want returned.
+ * @param nresult how many results are expected
+ * @param start first row id we want in the result.
+ * @param use_default_start if GNUNET_YES, then it will
+ * use the API call that requests /history omitting
+ * the 'start' argument. This makes easier to test
+ * the server default behaviour.
+ * @param nrows how many row we want to receive, at most.
+ */
+static struct TALER_TESTING_Command
+cmd_history2 (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ struct GNUNET_TIME_Absolute time,
+ unsigned int nresult,
+ unsigned long long start,
+ int use_default_start,
+ long long nrows)
+{
+ struct HistoryState *hs;
+
+ hs = GNUNET_new (struct HistoryState);
+ hs->http_status = http_status;
+ hs->time = time;
+ hs->nresult = nresult;
+ hs->start = start;
+ hs->nrows = nrows;
+ hs->merchant_url = merchant_url;
+ hs->use_default_start = use_default_start;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = hs,
+ .label = label,
+ .run = &history_run,
+ .cleanup = &history_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Make a "history" command.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * request.
+ * @param ctx CURL context.
+ * @param http_status expected HTTP response code
+ * @param time limit towards the past for the history
+ * records we want returned.
+ * @param nresult how many results are expected
+ * @param nrows how many row we want to receive, at most.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_history_default_start
+ (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ struct GNUNET_TIME_Absolute time,
+ unsigned int nresult,
+ long long nrows)
+{
+ return cmd_history2 (label,
+ merchant_url,
+ http_status,
+ time,
+ nresult,
+ -1, /* ignored */
+ GNUNET_YES,
+ nrows);
+}
+
+
+/**
+ * Make a "history" command.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * request.
+ * @param ctx CURL context.
+ * @param http_status expected HTTP response code
+ * @param time limit towards the past for the history
+ * records we want returned.
+ * @param nresult how many results are expected
+ * @param start first row id we want in the result.
+ * @param nrows how many row we want to receive, at most.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_history (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ struct GNUNET_TIME_Absolute time,
+ unsigned int nresult,
+ unsigned long long start,
+ long long nrows)
+{
+ return cmd_history2 (label,
+ merchant_url,
+ http_status,
+ time,
+ nresult,
+ start,
+ GNUNET_NO,
+ nrows);
+}
+
+
+/* end of testing_api_cmd_history.c */
diff --git a/src/testing/testing_api_cmd_pay.c b/src/testing/testing_api_cmd_pay.c
new file mode 100644
index 00000000..02bdf408
--- /dev/null
+++ b/src/testing/testing_api_cmd_pay.c
@@ -0,0 +1,875 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_pay.c
+ * @brief command to test the /pay feature.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_signatures.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+#define AMOUNT_WITH_FEE 0
+#define AMOUNT_WITHOUT_FEE 1
+#define REFUND_FEE 2
+
+/**
+ * State for a /pay CMD.
+ */
+struct PayState
+{
+ /**
+ * Contract terms hash code.
+ */
+ struct GNUNET_HashCode h_contract_terms;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Reference to a command that can provide a order id,
+ * typically a /proposal test command.
+ */
+ const char *proposal_reference;
+
+ /**
+ * Reference to a command that can provide a coin, so
+ * we can pay here.
+ */
+ const char *coin_reference;
+
+ /**
+ * The merchant base URL.
+ */
+ const char *merchant_url;
+
+ /**
+ * Amount to be paid, plus the deposit fee.
+ */
+ const char *amount_with_fee;
+
+ /**
+ * Amount to be paid, including NO fees.
+ */
+ const char *amount_without_fee;
+
+ /**
+ * Fee for refunding this payment.
+ */
+ const char *refund_fee;
+
+ /**
+ * Handle to the /pay operation.
+ */
+ struct TALER_MERCHANT_Pay *po;
+
+};
+
+
+/**
+ * State for a "pay again" CMD.
+ */
+struct PayAgainState
+{
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Reference to the "pay" command to abort.
+ */
+ const char *pay_reference;
+
+ /**
+ * Reference to the coins to use.
+ */
+ const char *coin_reference;
+
+ /**
+ * Merchant URL.
+ */
+ const char *merchant_url;
+
+ /**
+ * Refund fee.
+ */
+ const char *refund_fee;
+
+ /**
+ * Handle to a "pay again" operation.
+ */
+ struct TALER_MERCHANT_Pay *pao;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Parse the @a coins specification and grow the @a pc
+ * array with the coins found, updating @a npc.
+ *
+ * @param[in,out] pc pointer to array of coins found
+ * @param[in,out] npc length of array at @a pc
+ * @param[in] coins string specifying coins to add to @a pc,
+ * clobbered in the process
+ * @param is interpreter state
+ * @param amount_with_fee total amount to be paid for a contract.
+ * @param amount_without_fee to be removed, there is no
+ * per-contract fee, only per-coin exists.
+ * @param refund_fee per-contract? per-coin?
+ * @return #GNUNET_OK on success
+ */
+static int
+build_coins (struct TALER_MERCHANT_PayCoin **pc,
+ unsigned int *npc,
+ char *coins,
+ struct TALER_TESTING_Interpreter *is,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *refund_fee)
+{
+ char *token;
+
+ for (token = strtok (coins, ";");
+ NULL != token;
+ token = strtok (NULL, ";"))
+ {
+ const struct TALER_TESTING_Command *coin_cmd;
+ char *ctok;
+ unsigned int ci;
+ struct TALER_MERCHANT_PayCoin *icoin;
+ const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+
+ /* Token syntax is "LABEL[/NUMBER]" */
+ ctok = strchr (token, '/');
+ ci = 0;
+ if (NULL != ctok)
+ {
+ *ctok = '\0';
+ ctok++;
+ if (1 != sscanf (ctok,
+ "%u",
+ &ci))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ coin_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, token);
+
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_array_grow (*pc,
+ *npc,
+ (*npc) + 1);
+
+ icoin = &((*pc)[(*npc) - 1]);
+
+ {
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_DenominationSignature *denom_sig;
+ const struct TALER_Amount *denom_value;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_coin_priv
+ (coin_cmd, 0, &coin_priv));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_denom_pub
+ (coin_cmd, 0, &denom_pub));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_denom_sig
+ (coin_cmd, 0, &denom_sig));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_amount_obj
+ (coin_cmd, 0, &denom_value));
+
+ icoin->coin_priv = *coin_priv;
+ icoin->denom_pub = denom_pub->key;
+ icoin->denom_sig = *denom_sig;
+ icoin->denom_value = *denom_value;
+ icoin->amount_with_fee = *denom_value;
+ }
+ GNUNET_assert (NULL != (dpk =
+ TALER_TESTING_find_pk (is->keys,
+ &icoin->denom_value)));
+
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&icoin->amount_without_fee,
+ &icoin->denom_value,
+ &dpk->fee_deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_url (coin_cmd,
+ TALER_TESTING_UT_EXCHANGE_BASE_URL,
+ &icoin->exchange_url));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (refund_fee,
+ &icoin->refund_fee));
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with the result of a /pay operation.
+ * Checks whether the merchant signature is valid and the
+ * HTTP response code matches our expectation.
+ *
+ * @param cls closure with the interpreter state
+ * @param hr HTTP response
+ */
+static void
+pay_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PayState *ps = cls;
+ struct GNUNET_CRYPTO_EddsaSignature sig;
+ const char *error_name;
+ unsigned int error_line;
+ const struct TALER_MerchantPublicKeyP *merchant_pub;
+
+ ps->po = NULL;
+ if (ps->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (ps->is));
+ TALER_TESTING_FAIL (ps->is);
+ }
+ if (MHD_HTTP_OK == hr->http_status)
+ {
+ /* Check signature */
+ struct PaymentResponsePS mr;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("sig",
+ &sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &ps->h_contract_terms),
+ GNUNET_JSON_spec_end ()
+ };
+ const struct TALER_TESTING_Command *proposal_cmd;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_JSON_parse (hr->reply,
+ spec,
+ &error_name,
+ &error_line));
+ mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK);
+ mr.purpose.size = htonl (sizeof (mr));
+ mr.h_contract_terms = ps->h_contract_terms;
+
+ /* proposal reference was used at least once, at this point */
+ GNUNET_assert (NULL !=
+ (proposal_cmd =
+ TALER_TESTING_interpreter_lookup_command (ps->is,
+ ps->
+ proposal_reference)));
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_merchant_pub (proposal_cmd,
+ 0,
+ &merchant_pub))
+ TALER_TESTING_FAIL (ps->is);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
+ &mr,
+ &sig,
+ &merchant_pub->eddsa_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Merchant signature given in response to /pay invalid\n");
+ TALER_TESTING_FAIL (ps->is);
+ }
+ }
+
+ TALER_TESTING_interpreter_next (ps->is);
+}
+
+
+/**
+ * Function used by both "pay" and "abort" operations.
+ * It prepares data and sends the "pay" request to the
+ * backend.
+ *
+ * @param merchant_url base URL of the merchant serving the
+ * request.
+ * @param coin_reference reference to the CMD(s) that offer
+ * "coins" traits. It is possible to give multiple
+ * references by using semicolons to separate them.
+ * @param proposal_refere reference to a "proposal" CMD.
+ * @param is interpreter state.
+ * @param amount_with_fee amount to be paid, including deposit
+ * fee.
+ * @param amount_without_fee amount to be paid, without deposit
+ * fee.
+ * @param refund_fee refund fee.
+ * @param api_func "lib" function that will be called to either
+ * issue a "pay" or "abort" request.
+ * @param api_cb callback for @a api_func.
+ * @param api_cb_cls closure for @a api_cb
+ *
+ * @return handle to the operation, NULL if errors occur.
+ */
+static struct TALER_MERCHANT_Pay *
+_pay_run (const char *merchant_url,
+ const char *coin_reference,
+ const char *proposal_reference,
+ struct TALER_TESTING_Interpreter *is,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *refund_fee,
+ TALER_MERCHANT_PayCallback api_cb,
+ void *api_cb_cls)
+{
+ const struct TALER_TESTING_Command *proposal_cmd;
+ const json_t *contract_terms;
+ const char *order_id;
+ struct GNUNET_TIME_Absolute refund_deadline;
+ struct GNUNET_TIME_Absolute pay_deadline;
+ struct GNUNET_TIME_Absolute timestamp;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_HashCode h_wire;
+ const struct GNUNET_HashCode *h_proposal;
+ struct TALER_Amount total_amount;
+ struct TALER_Amount max_fee;
+ const char *error_name;
+ unsigned int error_line;
+ struct TALER_MERCHANT_PayCoin *pay_coins;
+ unsigned int npay_coins;
+ char *cr;
+ struct TALER_MerchantSignatureP *merchant_sig;
+ struct TALER_MERCHANT_Pay *ret;
+
+ proposal_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ proposal_reference);
+
+ if (NULL == proposal_cmd)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_contract_terms (proposal_cmd,
+ 0,
+ &contract_terms))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ {
+ /* Get information that needs to be put verbatim in the
+ * deposit permission */
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("order_id",
+ &order_id),
+ GNUNET_JSON_spec_absolute_time ("refund_deadline",
+ &refund_deadline),
+ GNUNET_JSON_spec_absolute_time ("pay_deadline",
+ &pay_deadline),
+ GNUNET_JSON_spec_absolute_time ("timestamp",
+ &timestamp),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &h_wire),
+ TALER_JSON_spec_amount ("amount",
+ &total_amount),
+ TALER_JSON_spec_amount ("max_fee",
+ &max_fee),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract_terms,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ char *js;
+
+ js = json_dumps (contract_terms,
+ JSON_INDENT (1));
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u for input `%s'\n",
+ error_name,
+ error_line,
+ js);
+ free (js);
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ }
+
+ cr = GNUNET_strdup (coin_reference);
+ pay_coins = NULL;
+ npay_coins = 0;
+ if (GNUNET_OK !=
+ build_coins (&pay_coins,
+ &npay_coins,
+ cr,
+ is,
+ amount_with_fee,
+ amount_without_fee,
+ refund_fee))
+ {
+ GNUNET_array_grow (pay_coins,
+ npay_coins,
+ 0);
+ GNUNET_free (cr);
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ GNUNET_free (cr);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_merchant_sig (proposal_cmd,
+ 0,
+ &merchant_sig))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
+ 0,
+ &h_proposal))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ ret = TALER_MERCHANT_pay_wallet (is->ctx,
+ merchant_url,
+ h_proposal,
+ &total_amount,
+ &max_fee,
+ &merchant_pub,
+ merchant_sig,
+ timestamp,
+ refund_deadline,
+ pay_deadline,
+ &h_wire,
+ order_id,
+ npay_coins,
+ pay_coins,
+ api_cb,
+ api_cb_cls);
+ GNUNET_array_grow (pay_coins,
+ npay_coins,
+ 0);
+ return ret;
+}
+
+
+/**
+ * Run a "pay" CMD.
+ *
+ * @param cls closure.
+ * @param cmd current CMD being run.
+ * @param is interpreter state.
+ */
+static void
+pay_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+
+ struct PayState *ps = cls;
+
+ ps->is = is;
+ if (NULL == (ps->po = _pay_run (ps->merchant_url,
+ ps->coin_reference,
+ ps->proposal_reference,
+ is,
+ ps->amount_with_fee,
+ ps->amount_without_fee,
+ ps->refund_fee,
+ &pay_cb,
+ ps)))
+ TALER_TESTING_FAIL (is);
+}
+
+
+/**
+ * Free a "pay" CMD, and cancel it if need be.
+ *
+ * @param cls closure.
+ * @param cmd command currently being freed.
+ */
+static void
+pay_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PayState *ps = cls;
+
+ if (NULL != ps->po)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command `%s' did not complete.\n",
+ TALER_TESTING_interpreter_get_current_label (
+ ps->is));
+ TALER_MERCHANT_pay_cancel (ps->po);
+ }
+
+ GNUNET_free (ps);
+}
+
+
+/**
+ * Offer internal data useful to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+pay_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+
+ struct PayState *ps = cls;
+ const char *order_id;
+ const struct TALER_TESTING_Command *proposal_cmd;
+ const struct TALER_MerchantPublicKeyP *merchant_pub;
+
+ if (NULL ==
+ (proposal_cmd = TALER_TESTING_interpreter_lookup_command
+ (ps->is, ps->proposal_reference)))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_order_id
+ (proposal_cmd, 0, &order_id))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_merchant_pub
+ (proposal_cmd,
+ 0,
+ &merchant_pub))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_string
+ (AMOUNT_WITH_FEE, ps->amount_with_fee),
+ TALER_TESTING_make_trait_string
+ (AMOUNT_WITHOUT_FEE, ps->amount_without_fee),
+ TALER_TESTING_make_trait_string
+ (REFUND_FEE, ps->refund_fee),
+ TALER_TESTING_make_trait_proposal_reference
+ (0, ps->proposal_reference),
+ TALER_TESTING_make_trait_coin_reference
+ (0, ps->coin_reference),
+ TALER_TESTING_make_trait_order_id (0, order_id),
+ TALER_TESTING_make_trait_merchant_pub (0, merchant_pub),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
+}
+
+
+/**
+ * Make a "pay" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference the proposal whose payment status
+ * is going to be checked.
+ * @param coin_reference reference to any command which is able
+ * to provide coins to use for paying.
+ * @param amount_with_fee amount to pay, including the deposit
+ * fee
+ * @param amount_without_fee amount to pay, no fees included.
+ * @param refund_fee fee for refunding this payment.
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *coin_reference,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *refund_fee)
+{
+ struct PayState *ps;
+
+ ps = GNUNET_new (struct PayState);
+ ps->http_status = http_status;
+ ps->proposal_reference = proposal_reference;
+ ps->coin_reference = coin_reference;
+ ps->merchant_url = merchant_url;
+ ps->amount_with_fee = amount_with_fee;
+ ps->amount_without_fee = amount_without_fee;
+ ps->refund_fee = refund_fee;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &pay_run,
+ .cleanup = &pay_cleanup,
+ .traits = &pay_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Function called with the result of a /pay again operation,
+ * check signature and HTTP response code are good.
+ *
+ * @param cls closure with the interpreter state
+ * @param hr HTTP response
+ */
+static void
+pay_again_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PayAgainState *pas = cls;
+ struct GNUNET_CRYPTO_EddsaSignature sig;
+ const char *error_name;
+ unsigned int error_line;
+ const struct TALER_TESTING_Command *pay_cmd;
+ const struct TALER_MerchantPublicKeyP *merchant_pub;
+
+ pas->pao = NULL;
+ if (pas->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (pas->is));
+ TALER_TESTING_interpreter_fail (pas->is);
+ return;
+ }
+
+ if (NULL ==
+ (pay_cmd = TALER_TESTING_interpreter_lookup_command (pas->is,
+ pas->pay_reference)))
+ TALER_TESTING_FAIL (pas->is);
+
+ if (MHD_HTTP_OK == hr->http_status)
+ {
+ struct PaymentResponsePS mr;
+ /* Check signature */
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("sig",
+ &sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &mr.h_contract_terms),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (hr->reply,
+ spec,
+ &error_name,
+ &error_line));
+ mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK);
+ mr.purpose.size = htonl (sizeof (mr));
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_merchant_pub (pay_cmd,
+ 0,
+ &merchant_pub))
+ TALER_TESTING_FAIL (pas->is);
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
+ &mr,
+ &sig,
+ &merchant_pub->eddsa_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Merchant signature given in"
+ " response to /pay invalid\n");
+ TALER_TESTING_FAIL (pas->is);
+ }
+ }
+
+ TALER_TESTING_interpreter_next (pas->is);
+}
+
+
+/**
+ * Run a "pay again" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+pay_again_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PayAgainState *pas = cls;
+ const struct TALER_TESTING_Command *pay_cmd;
+ const char *proposal_reference;
+ const char *amount_with_fee;
+ const char *amount_without_fee;
+
+ pas->is = is;
+ pay_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, pas->pay_reference);
+ if (NULL == pay_cmd)
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_proposal_reference
+ (pay_cmd, 0, &proposal_reference))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_string
+ (pay_cmd, AMOUNT_WITH_FEE, &amount_with_fee))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_string
+ (pay_cmd, AMOUNT_WITHOUT_FEE, &amount_without_fee))
+ TALER_TESTING_FAIL (is);
+
+ if (NULL == (pas->pao = _pay_run (pas->merchant_url,
+ pas->coin_reference,
+ proposal_reference,
+ is,
+ amount_with_fee,
+ amount_without_fee,
+ pas->refund_fee,
+ &pay_again_cb,
+ pas)))
+ TALER_TESTING_FAIL (is);
+}
+
+
+/**
+ * Free and possibly cancel a "pay again" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being freed.
+ */
+static void
+pay_again_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PayAgainState *pas = cls;
+
+ if (NULL != pas->pao)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command `%s' did not complete.\n",
+ TALER_TESTING_interpreter_get_current_label (
+ pas->is));
+ TALER_MERCHANT_pay_cancel (pas->pao);
+ }
+ GNUNET_free (pas);
+}
+
+
+/**
+ * Make a "pay again" test command. Its purpose is to
+ * take all the data from a aborted "pay" CMD, and use
+ * good coins - found in @a coin_reference - to correctly
+ * pay for it.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param pay_reference reference to the payment to replay
+ * @param coin_reference reference to the coins to use
+ * @param http_status expected HTTP response code
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_again (const char *label,
+ const char *merchant_url,
+ const char *pay_reference,
+ const char *coin_reference,
+ const char *refund_fee,
+ unsigned int http_status)
+{
+ struct PayAgainState *pas;
+
+ pas = GNUNET_new (struct PayAgainState);
+ pas->http_status = http_status;
+ pas->pay_reference = pay_reference;
+ pas->coin_reference = coin_reference;
+ pas->merchant_url = merchant_url;
+ pas->refund_fee = refund_fee;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pas,
+ .label = label,
+ .run = &pay_again_run,
+ .cleanup = &pay_again_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_pay.c */
diff --git a/src/testing/testing_api_cmd_pay_abort.c b/src/testing/testing_api_cmd_pay_abort.c
new file mode 100644
index 00000000..5911dd1d
--- /dev/null
+++ b/src/testing/testing_api_cmd_pay_abort.c
@@ -0,0 +1,595 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018, 2020 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_pay_abort.c
+ * @brief command to test the /pay abort feature.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_signatures.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+#define AMOUNT_WITH_FEE 0
+#define AMOUNT_WITHOUT_FEE 1
+#define REFUND_FEE 2
+
+/**
+ * State for a "pay abort" CMD.
+ */
+struct PayAbortState
+{
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Reference to the "pay" command to abort.
+ */
+ const char *pay_reference;
+
+ /**
+ * Merchant URL.
+ */
+ const char *merchant_url;
+
+ /**
+ * Handle to a "pay abort" operation.
+ */
+ struct TALER_MERCHANT_Pay *pao;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * How many refund permissions this CMD got
+ * the right for. Roughly, there is one refund
+ * permission for one coin.
+ */
+ unsigned int num_refunds;
+
+ /**
+ * The actual refund data.
+ */
+ struct TALER_MERCHANT_RefundEntry *res;
+
+ /**
+ * The contract whose payment is to be aborted.
+ */
+ struct GNUNET_HashCode h_contract;
+
+ /**
+ * Merchant public key.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+};
+
+
+/**
+ * Parse the @a coins specification and grow the @a pc
+ * array with the coins found, updating @a npc.
+ *
+ * @param[in,out] pc pointer to array of coins found
+ * @param[in,out] npc length of array at @a pc
+ * @param[in] coins string specifying coins to add to @a pc,
+ * clobbered in the process
+ * @param is interpreter state
+ * @param amount_with_fee total amount to be paid for a contract.
+ * @param amount_without_fee to be removed, there is no
+ * per-contract fee, only per-coin exists.
+ * @param refund_fee per-contract? per-coin?
+ * @return #GNUNET_OK on success
+ */
+static int
+build_coins (struct TALER_MERCHANT_PayCoin **pc,
+ unsigned int *npc,
+ char *coins,
+ struct TALER_TESTING_Interpreter *is,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *refund_fee)
+{
+ char *token;
+
+ for (token = strtok (coins, ";");
+ NULL != token;
+ token = strtok (NULL, ";"))
+ {
+ const struct TALER_TESTING_Command *coin_cmd;
+ char *ctok;
+ unsigned int ci;
+ struct TALER_MERCHANT_PayCoin *icoin;
+ const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+
+ /* Token syntax is "LABEL[/NUMBER]" */
+ ctok = strchr (token, '/');
+ ci = 0;
+ if (NULL != ctok)
+ {
+ *ctok = '\0';
+ ctok++;
+ if (1 != sscanf (ctok,
+ "%u",
+ &ci))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ coin_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, token);
+
+ if (NULL == coin_cmd)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_array_grow (*pc,
+ *npc,
+ (*npc) + 1);
+
+ icoin = &((*pc)[(*npc) - 1]);
+
+ {
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_DenominationSignature *denom_sig;
+ const struct TALER_Amount *denom_value;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_coin_priv
+ (coin_cmd, 0, &coin_priv));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_denom_pub
+ (coin_cmd, 0, &denom_pub));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_denom_sig
+ (coin_cmd, 0, &denom_sig));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_amount_obj
+ (coin_cmd, 0, &denom_value));
+
+ icoin->coin_priv = *coin_priv;
+ icoin->denom_pub = denom_pub->key;
+ icoin->denom_sig = *denom_sig;
+ icoin->denom_value = *denom_value;
+ icoin->amount_with_fee = *denom_value;
+ }
+ GNUNET_assert (NULL != (dpk =
+ TALER_TESTING_find_pk (is->keys,
+ &icoin->denom_value)));
+
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&icoin->amount_without_fee,
+ &icoin->denom_value,
+ &dpk->fee_deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_url (coin_cmd,
+ TALER_TESTING_UT_EXCHANGE_BASE_URL,
+ &icoin->exchange_url));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (refund_fee,
+ &icoin->refund_fee));
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Callback for a "pay abort" operation. Mainly, check HTTP
+ * response code was as expected and stores refund permissions
+ * in the state.
+ *
+ * @param cls closure.
+ * @param hr HTTP response
+ * @param merchant_pub public key of the merchant refunding the
+ * contract.
+ * @param h_contract the contract involved in the refund.
+ * @param num_refunds how many refund permissions have been
+ * issued.
+ * @param res array containing the refund permissions.
+ */
+static void
+pay_abort_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct GNUNET_HashCode *h_contract,
+ unsigned int num_refunds,
+ const struct TALER_MERCHANT_RefundEntry *res)
+{
+ struct PayAbortState *pas = cls;
+
+ pas->pao = NULL;
+ if (pas->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (pas->is));
+ TALER_TESTING_FAIL (pas->is);
+ }
+ if ( (MHD_HTTP_OK == hr->http_status) &&
+ (TALER_EC_NONE == hr->ec) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received %u refunds\n",
+ num_refunds);
+ pas->num_refunds = num_refunds;
+ pas->res = GNUNET_new_array (num_refunds,
+ struct TALER_MERCHANT_RefundEntry);
+ memcpy (pas->res,
+ res,
+ num_refunds * sizeof (struct TALER_MERCHANT_RefundEntry));
+ pas->h_contract = *h_contract;
+ pas->merchant_pub = *merchant_pub;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Successful pay-abort (HTTP status: %u)\n",
+ hr->http_status);
+ TALER_TESTING_interpreter_next (pas->is);
+}
+
+
+/**
+ * Function used by both the "abort" operation.
+ * It prepares data and sends the "pay-abort" request to the
+ * backend.
+ *
+ * @param merchant_url base URL of the merchant serving the
+ * request.
+ * @param coin_reference reference to the CMD(s) that offer
+ * "coins" traits. It is possible to give multiple
+ * references by using semicolons to separate them.
+ * @param proposal_refere reference to a "proposal" CMD.
+ * @param is interpreter state.
+ * @param amount_with_fee amount to be paid, including deposit
+ * fee.
+ * @param amount_without_fee amount to be paid, without deposit
+ * fee.
+ * @param refund_fee refund fee.
+ * @param api_func "lib" function that will be called to either
+ * issue a "pay" or "abort" request.
+ * @param api_cb callback for @a api_func.
+ * @param api_cb_cls closure for @a api_cb
+ * @return handle to the operation, NULL if errors occur.
+ */
+static struct TALER_MERCHANT_Pay *
+_pay_abort_run (const char *merchant_url,
+ const char *coin_reference,
+ const char *proposal_reference,
+ struct TALER_TESTING_Interpreter *is,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *refund_fee,
+ TALER_MERCHANT_PayRefundCallback api_cb,
+ void *api_cb_cls)
+{
+ const struct TALER_TESTING_Command *proposal_cmd;
+ const json_t *contract_terms;
+ const char *order_id;
+ struct GNUNET_TIME_Absolute refund_deadline;
+ struct GNUNET_TIME_Absolute pay_deadline;
+ struct GNUNET_TIME_Absolute timestamp;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_HashCode h_wire;
+ const struct GNUNET_HashCode *h_proposal;
+ struct TALER_Amount total_amount;
+ struct TALER_Amount max_fee;
+ const char *error_name;
+ unsigned int error_line;
+ struct TALER_MERCHANT_PayCoin *pay_coins;
+ unsigned int npay_coins;
+ char *cr;
+ struct TALER_MerchantSignatureP *merchant_sig;
+ struct TALER_MERCHANT_Pay *ret;
+
+ proposal_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ proposal_reference);
+
+ if (NULL == proposal_cmd)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_contract_terms (proposal_cmd,
+ 0,
+ &contract_terms))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ {
+ /* Get information that needs to be put verbatim in the
+ * deposit permission */
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("order_id",
+ &order_id),
+ GNUNET_JSON_spec_absolute_time ("refund_deadline",
+ &refund_deadline),
+ GNUNET_JSON_spec_absolute_time ("pay_deadline",
+ &pay_deadline),
+ GNUNET_JSON_spec_absolute_time ("timestamp",
+ &timestamp),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &h_wire),
+ TALER_JSON_spec_amount ("amount",
+ &total_amount),
+ TALER_JSON_spec_amount ("max_fee",
+ &max_fee),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract_terms,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ char *js;
+
+ js = json_dumps (contract_terms,
+ JSON_INDENT (1));
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u for input `%s'\n",
+ error_name,
+ error_line,
+ js);
+ free (js);
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ }
+
+ cr = GNUNET_strdup (coin_reference);
+ pay_coins = NULL;
+ npay_coins = 0;
+ if (GNUNET_OK !=
+ build_coins (&pay_coins,
+ &npay_coins,
+ cr,
+ is,
+ amount_with_fee,
+ amount_without_fee,
+ refund_fee))
+ {
+ GNUNET_array_grow (pay_coins,
+ npay_coins,
+ 0);
+ GNUNET_free (cr);
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ GNUNET_free (cr);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_merchant_sig (proposal_cmd,
+ 0,
+ &merchant_sig))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
+ 0,
+ &h_proposal))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ ret = TALER_MERCHANT_pay_abort (is->ctx,
+ merchant_url,
+ h_proposal,
+ &total_amount,
+ &max_fee,
+ &merchant_pub,
+ merchant_sig,
+ timestamp,
+ refund_deadline,
+ pay_deadline,
+ &h_wire,
+ order_id,
+ npay_coins,
+ pay_coins,
+ api_cb,
+ api_cb_cls);
+ GNUNET_array_grow (pay_coins,
+ npay_coins,
+ 0);
+ return ret;
+}
+
+
+/**
+ * Free a "pay abort" CMD, and cancel it if need be.
+ *
+ * @param cls closure.
+ * @param cmd command currently being freed.
+ */
+static void
+pay_abort_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PayAbortState *pas = cls;
+
+ if (NULL != pas->pao)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command `%s' did not complete.\n",
+ TALER_TESTING_interpreter_get_current_label (
+ pas->is));
+ TALER_MERCHANT_pay_cancel (pas->pao);
+ }
+ GNUNET_free_non_null (pas->res);
+ GNUNET_free (pas);
+}
+
+
+/**
+ * Run a "pay abort" CMD.
+ *
+ * @param cls closure
+ * @param cmd command being run.
+ * @param is interpreter state
+ */
+static void
+pay_abort_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+
+ struct PayAbortState *pas = cls;
+ const struct TALER_TESTING_Command *pay_cmd;
+
+ const char *proposal_reference;
+ const char *coin_reference;
+ const char *amount_with_fee;
+ const char *amount_without_fee;
+ const char *refund_fee;
+
+ pas->is = is;
+ pay_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, pas->pay_reference);
+ if (NULL == pay_cmd)
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_proposal_reference
+ (pay_cmd, 0, &proposal_reference))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_coin_reference
+ (pay_cmd, 0, &coin_reference))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_string
+ (pay_cmd, AMOUNT_WITH_FEE, &amount_with_fee))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_string
+ (pay_cmd, AMOUNT_WITHOUT_FEE, &amount_without_fee))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_string
+ (pay_cmd, REFUND_FEE, &refund_fee))
+ TALER_TESTING_FAIL (is);
+
+ if (NULL == (pas->pao = _pay_abort_run (pas->merchant_url,
+ coin_reference,
+ proposal_reference,
+ is,
+ amount_with_fee,
+ amount_without_fee,
+ refund_fee,
+ &pay_abort_cb,
+ pas)))
+ TALER_TESTING_FAIL (is);
+}
+
+
+/**
+ * Offer internal data useful to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+pay_abort_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PayAbortState *pas = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_merchant_pub
+ (0, &pas->merchant_pub),
+ TALER_TESTING_make_trait_h_contract_terms
+ (0, &pas->h_contract),
+ TALER_TESTING_make_trait_refund_entry
+ (0, pas->res),
+ TALER_TESTING_make_trait_uint (0, &pas->num_refunds),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Make a "pay abort" test command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param pay_reference reference to the payment to abort
+ * @param http_status expected HTTP response code
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_abort (const char *label,
+ const char *merchant_url,
+ const char *pay_reference,
+ unsigned int http_status)
+{
+ struct PayAbortState *pas;
+
+ pas = GNUNET_new (struct PayAbortState);
+ pas->http_status = http_status;
+ pas->pay_reference = pay_reference;
+ pas->merchant_url = merchant_url;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pas,
+ .label = label,
+ .run = &pay_abort_run,
+ .cleanup = &pay_abort_cleanup,
+ .traits = &pay_abort_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_pay_abort.c */
diff --git a/src/testing/testing_api_cmd_pay_abort_refund.c b/src/testing/testing_api_cmd_pay_abort_refund.c
new file mode 100644
index 00000000..918ad758
--- /dev/null
+++ b/src/testing/testing_api_cmd_pay_abort_refund.c
@@ -0,0 +1,248 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_pay_abort_refund.c
+ * @brief command to test the pay-abort-refund feature.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_signatures.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a "pay abort refund" CMD. This command
+ * takes the refund permissions from a "pay abort" CMD,
+ * and redeems those at the exchange.
+ */
+struct PayAbortRefundState
+{
+
+ /**
+ * "abort" CMD that will provide with refund permissions.
+ */
+ const char *abort_reference;
+
+ /**
+ * Expected number of coins that were refunded.
+ * Only used to counter-check, not to perform any
+ * operation.
+ */
+ unsigned int num_coins;
+
+ /**
+ * The amount to be "withdrawn" from the refund session.
+ */
+ const char *refund_amount;
+
+ /**
+ * The refund fee (charged to the merchant).
+ */
+ const char *refund_fee;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Handle to the refund operation.
+ */
+ struct TALER_EXCHANGE_RefundHandle *rh;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+};
+
+
+/**
+ * Callback used to work out the response from the exchange
+ * to a refund operation. Currently only checks if the response
+ * code is as expected.
+ *
+ * @param cls closure
+ * @param hr HTTP response code details
+ * @param sign_key exchange key used to sign @a obj, or NULL
+ * @param signature the actual signature, or NULL on error
+ */
+static void
+abort_refund_cb (void *cls,
+ const struct TALER_EXCHANGE_HttpResponse *hr,
+ const struct TALER_ExchangePublicKeyP *sign_key,
+ const struct TALER_ExchangeSignatureP *signature)
+{
+ struct PayAbortRefundState *pars = cls;
+
+ pars->rh = NULL;
+ if (pars->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ hr->ec,
+ TALER_TESTING_interpreter_get_current_label (pars->is));
+ TALER_TESTING_interpreter_fail (pars->is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (pars->is);
+}
+
+
+/**
+ * Free the state of a "pay abort refund" CMD, and possibly
+ * cancel a pending operation.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being freed.
+ */
+static void
+pay_abort_refund_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PayAbortRefundState *pars = cls;
+
+ if (NULL != pars->rh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command `%s' did not complete.\n",
+ TALER_TESTING_interpreter_get_current_label (
+ pars->is));
+ TALER_EXCHANGE_refund_cancel (pars->rh);
+ }
+ GNUNET_free (pars);
+}
+
+
+/**
+ * Run a "pay abort refund" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+pay_abort_refund_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PayAbortRefundState *pars = cls;
+ struct TALER_Amount refund_fee;
+ struct TALER_Amount refund_amount;
+ const struct TALER_MERCHANT_RefundEntry *refund_entry;
+ const unsigned int *num_refunds;
+ const struct TALER_TESTING_Command *abort_cmd;
+ const struct TALER_MerchantPublicKeyP *merchant_pub;
+ const struct GNUNET_HashCode *h_contract_terms;
+
+ pars->is = is;
+ if (NULL ==
+ (abort_cmd =
+ TALER_TESTING_interpreter_lookup_command (is,
+ pars->abort_reference)) )
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_uint (abort_cmd,
+ 0,
+ &num_refunds))
+ TALER_TESTING_FAIL (is);
+ if (pars->num_coins >= *num_refunds)
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_contract_terms (abort_cmd,
+ 0,
+ &h_contract_terms))
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_merchant_pub (abort_cmd,
+ 0,
+ &merchant_pub))
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_refund_entry (abort_cmd,
+ 0,
+ &refund_entry))
+ TALER_TESTING_FAIL (is);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (pars->refund_amount,
+ &refund_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (pars->refund_fee,
+ &refund_fee));
+ pars->rh = TALER_EXCHANGE_refund2 (is->exchange,
+ &refund_amount,
+ &refund_fee,
+ h_contract_terms,
+ &refund_entry->coin_pub,
+ refund_entry->rtransaction_id,
+ merchant_pub,
+ &refund_entry->merchant_sig,
+ &abort_refund_cb,
+ pars);
+ GNUNET_assert (NULL != pars->rh);
+}
+
+
+/**
+ * Make a "pay abort refund" CMD. This command uses the
+ * refund permission from a "pay abort" CMD, and redeems it
+ * at the exchange.
+ *
+ * @param label command label.
+ * @param abort_reference reference to the "pay abort" CMD that
+ * will offer the refund permission.
+ * @param num_coins how many coins are expected to be refunded.
+ * @param refund_amount the amount we are going to redeem as
+ * refund.
+ * @param refund_fee the refund fee (merchant pays it)
+ * @param http_status expected HTTP response code.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_abort_refund
+ (const char *label,
+ const char *abort_reference,
+ unsigned int num_coins,
+ const char *refund_amount,
+ const char *refund_fee,
+ unsigned int http_status)
+{
+ struct PayAbortRefundState *pars;
+
+ pars = GNUNET_new (struct PayAbortRefundState);
+ pars->abort_reference = abort_reference;
+ pars->num_coins = num_coins;
+ pars->refund_amount = refund_amount;
+ pars->refund_fee = refund_fee;
+ pars->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pars,
+ .label = label,
+ .run = &pay_abort_refund_run,
+ .cleanup = &pay_abort_refund_cleanup
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_poll_payment.c b/src/testing/testing_api_cmd_poll_payment.c
new file mode 100644
index 00000000..9575e32e
--- /dev/null
+++ b/src/testing/testing_api_cmd_poll_payment.c
@@ -0,0 +1,491 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_poll_payment.c
+ * @brief command to test the /public/poll-payment feature.
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_signatures.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a /poll-payment conclude CMD.
+ */
+struct PollPaymentConcludeState;
+
+
+/**
+ * State for a /poll-payment start CMD.
+ */
+struct PollPaymentStartState
+{
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_MERCHANT_PollPaymentOperation *cpo;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to a command that can provide a order id,
+ * typically a /proposal test command.
+ */
+ const char *proposal_reference;
+
+ /**
+ * The merchant base URL.
+ */
+ const char *merchant_url;
+
+ /**
+ * Conclude state waiting for completion (if any).
+ */
+ struct PollPaymentConcludeState *cs;
+
+ /**
+ * How long is the long-polling allowed to take?
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * Set to the start time of the @e cpo plus the @e timeout.
+ */
+ struct GNUNET_TIME_Absolute deadline;
+
+ /**
+ * Amount refunded, set if @e refunded is #GNUNET_YES
+ */
+ struct TALER_Amount refund;
+
+ /**
+ * Refund to wait for, set if @e wait_for_refund is #GNUNET_YES
+ */
+ struct TALER_Amount min_refund;
+
+ /**
+ * Final HTTP response status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * #GNUNET_YES if the proposal was paid.
+ */
+ int paid;
+
+ /**
+ * #GNUNET_YES if the proposal was paid and then refunded
+ */
+ int refunded;
+
+ /**
+ * #GNUNET_YES if we are waiting for a refund.
+ */
+ int wait_for_refund;
+
+};
+
+
+/**
+ * State for a /poll-payment conclude CMD.
+ */
+struct PollPaymentConcludeState
+{
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to a command that can provide a poll payment start command.
+ */
+ const char *start_reference;
+
+ /**
+ * Task to wait for the deadline.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Expected HTTP response status code.
+ */
+ unsigned int expected_http_status;
+
+ /**
+ * #GNUNET_YES if the proposal was expected to be paid.
+ */
+ int expected_paid;
+
+};
+
+
+/**
+ * Free a /poll-payment CMD, and possibly cancel a pending
+ * operation thereof.
+ *
+ * @param cls closure
+ * @param cmd the command currently getting freed.
+ */
+static void
+poll_payment_start_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PollPaymentStartState *cps = cls;
+
+ if (NULL != cps->cpo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Command `%s' was not terminated\n",
+ TALER_TESTING_interpreter_get_current_label (
+ cps->is));
+ TALER_MERCHANT_poll_payment_cancel (cps->cpo);
+ }
+ GNUNET_free (cps);
+}
+
+
+/**
+ * Task called when either the timeout for the /poll-payment
+ * command expired or we got a response. Checks if the
+ * result is what we expected.
+ *
+ * @param cls a `struct PollPaymentConcludeState`
+ */
+static void
+conclude_task (void *cls)
+{
+ struct PollPaymentConcludeState *ppc = cls;
+ const struct TALER_TESTING_Command *poll_cmd;
+ struct PollPaymentStartState *cps;
+ struct GNUNET_TIME_Absolute now;
+
+ ppc->task = NULL;
+ poll_cmd =
+ TALER_TESTING_interpreter_lookup_command (ppc->is,
+ ppc->start_reference);
+ if (NULL == poll_cmd)
+ TALER_TESTING_FAIL (ppc->is);
+ cps = poll_cmd->cls;
+ if (NULL != cps->cpo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected /poll/payment to have completed, but it did not!\n");
+ TALER_TESTING_FAIL (ppc->is);
+ }
+ if (cps->http_status != ppc->expected_http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected HTTP status %u, got %u\n",
+ ppc->expected_http_status,
+ cps->http_status);
+ TALER_TESTING_FAIL (ppc->is);
+ }
+ now = GNUNET_TIME_absolute_get ();
+ if ( (GNUNET_NO == cps->paid) &&
+ (GNUNET_TIME_absolute_add (cps->deadline,
+ GNUNET_TIME_UNIT_SECONDS).abs_value_us <
+ now.abs_value_us) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected answer to be delayed until %llu, but got response at %llu\n",
+ (unsigned long long) cps->deadline.abs_value_us,
+ (unsigned long long) now.abs_value_us);
+ TALER_TESTING_FAIL (ppc->is);
+ }
+ if (cps->paid != ppc->expected_paid)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected paid status %u, got %u\n",
+ ppc->expected_paid,
+ cps->paid);
+ TALER_TESTING_FAIL (ppc->is);
+ }
+ TALER_TESTING_interpreter_next (ppc->is);
+}
+
+
+/**
+ * Callback for a /poll-payment request.
+ *
+ * @param cls closure.
+ * @param hr HTTP response we got
+ * @param paid #GNUNET_YES (#GNUNET_NO) if the contract was (not) paid
+ * @param refunded #GNUNET_YES (#GNUNET_NO) if the contract was
+ * (not) refunded.
+ * @param refund_amount the amount that was refunded to this
+ * contract.
+ * @param taler_pay_uri the URI that instructs the wallets to process
+ * the payment
+ */
+static void
+poll_payment_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ int paid,
+ int refunded,
+ struct TALER_Amount *refund_amount,
+ const char *taler_pay_uri)
+{
+ struct PollPaymentStartState *cps = cls;
+
+ cps->cpo = NULL;
+ if ( (MHD_HTTP_OK != hr->http_status) &&
+ (NULL != hr->reply) )
+ {
+ char *log = json_dumps (hr->reply,
+ JSON_COMPACT);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Poll payment returned %u: %s\n",
+ hr->http_status,
+ log);
+ free (log);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Poll payment returned %u (%d/%d)\n",
+ hr->http_status,
+ paid,
+ refunded);
+ }
+ cps->paid = paid;
+ cps->http_status = hr->http_status;
+ cps->refunded = refunded;
+ if (GNUNET_YES == refunded)
+ cps->refund = *refund_amount;
+ if (NULL != cps->cs)
+ {
+ GNUNET_SCHEDULER_cancel (cps->cs->task);
+ cps->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
+ cps->cs);
+ }
+}
+
+
+/**
+ * Run a /poll-payment CMD.
+ *
+ * @param cmd the command currently being run.
+ * @param cls closure.
+ * @param is interpreter state.
+ */
+static void
+poll_payment_start_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PollPaymentStartState *cps = cls;
+ const struct TALER_TESTING_Command *proposal_cmd;
+ const char *order_id;
+ const struct GNUNET_HashCode *h_contract;
+
+ cps->is = is;
+ proposal_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ cps->proposal_reference);
+ if (NULL == proposal_cmd)
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_order_id (proposal_cmd,
+ 0,
+ &order_id))
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
+ 0,
+ &h_contract))
+ TALER_TESTING_FAIL (is);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Polling for order id `%s'\n",
+ order_id);
+ /* add 1s grace time to timeout */
+ cps->deadline
+ = GNUNET_TIME_absolute_add (GNUNET_TIME_relative_to_absolute (cps->timeout),
+ GNUNET_TIME_UNIT_SECONDS);
+ cps->cpo = TALER_MERCHANT_poll_payment (is->ctx,
+ cps->merchant_url,
+ order_id,
+ h_contract,
+ NULL, /* session id */
+ cps->timeout,
+ (GNUNET_YES == cps->wait_for_refund)
+ ? &cps->min_refund
+ : NULL,
+ &poll_payment_cb,
+ cps);
+ GNUNET_assert (NULL != cps->cpo);
+ /* We CONTINUE to run the interpreter while the long-polled command
+ completes asynchronously! */
+ TALER_TESTING_interpreter_next (cps->is);
+}
+
+
+/**
+ * Start a long-polled "poll-payment" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param proposal_reference the proposal whose payment status
+ * is going to be checked.
+ * @param min_refund minimum refund to wait for
+ * @param timeout which timeout to use
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_payment_start (const char *label,
+ const char *merchant_url,
+ const char *proposal_reference,
+ const char *min_refund,
+ struct GNUNET_TIME_Relative timeout)
+{
+ struct PollPaymentStartState *cps;
+
+ cps = GNUNET_new (struct PollPaymentStartState);
+ cps->proposal_reference = proposal_reference;
+ cps->merchant_url = merchant_url;
+ cps->timeout = timeout;
+ if (NULL != min_refund)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (min_refund,
+ &cps->min_refund));
+ cps->wait_for_refund = GNUNET_YES;
+ }
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cps,
+ .label = label,
+ .run = &poll_payment_start_run,
+ .cleanup = &poll_payment_start_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Free a /poll-payment CMD, and possibly cancel a pending
+ * operation thereof.
+ *
+ * @param cls closure
+ * @param cmd the command currently getting freed.
+ */
+static void
+poll_payment_conclude_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PollPaymentConcludeState *cps = cls;
+
+ if (NULL != cps->task)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Command `%s' was not terminated\n",
+ TALER_TESTING_interpreter_get_current_label (
+ cps->is));
+ GNUNET_SCHEDULER_cancel (cps->task);
+ cps->task = NULL;
+ }
+ GNUNET_free (cps);
+}
+
+
+/**
+ * Run a /poll-payment CMD.
+ *
+ * @param cmd the command currently being run.
+ * @param cls closure.
+ * @param is interpreter state.
+ */
+static void
+poll_payment_conclude_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PollPaymentConcludeState *ppc = cls;
+ const struct TALER_TESTING_Command *poll_cmd;
+ struct PollPaymentStartState *cps;
+
+ ppc->is = is;
+ poll_cmd =
+ TALER_TESTING_interpreter_lookup_command (is,
+ ppc->start_reference);
+ if (NULL == poll_cmd)
+ TALER_TESTING_FAIL (ppc->is);
+ GNUNET_assert (poll_cmd->run == &poll_payment_start_run);
+ cps = poll_cmd->cls;
+ cps->cs = ppc;
+ if (NULL == cps->cpo)
+ ppc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
+ ppc);
+ else
+ ppc->task = GNUNET_SCHEDULER_add_at (cps->deadline,
+ &conclude_task,
+ ppc);
+}
+
+
+/**
+ * Expect completion of a long-polled "poll payment" test command.
+ *
+ * @param label command label.
+ * @param poll_start_reference payment start operation that should have
+ * completed
+ * @param http_status expected HTTP response code.
+ * @param expect_paid #GNUNET_YES if we expect the proposal to be
+ * paid, #GNUNET_NO otherwise.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_payment_conclude (const char *label,
+ unsigned int http_status,
+ const char *poll_start_reference,
+ unsigned int expect_paid)
+{
+ struct PollPaymentConcludeState *cps;
+
+ cps = GNUNET_new (struct PollPaymentConcludeState);
+ cps->start_reference = poll_start_reference;
+ cps->expected_paid = expect_paid;
+ cps->expected_http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cps,
+ .label = label,
+ .run = &poll_payment_conclude_run,
+ .cleanup = &poll_payment_conclude_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_poll_payment.c */
diff --git a/src/testing/testing_api_cmd_proposal.c b/src/testing/testing_api_cmd_proposal.c
new file mode 100644
index 00000000..fd53db2a
--- /dev/null
+++ b/src/testing/testing_api_cmd_proposal.c
@@ -0,0 +1,401 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange/testing_api_cmd_proposal.c
+ * @brief command to run /proposal
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * State for a "proposal" CMD.
+ */
+struct ProposalState
+{
+
+ /**
+ * The order.
+ */
+ const char *order;
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Order id.
+ */
+ const char *order_id;
+
+ /**
+ * Contract terms obtained from the backend.
+ */
+ json_t *contract_terms;
+
+ /**
+ * Contract terms hash code.
+ */
+ struct GNUNET_HashCode h_contract_terms;
+
+ /**
+ * The /proposal operation handle.
+ */
+ struct TALER_MERCHANT_ProposalOperation *po;
+
+ /**
+ * The (initial) /proposal/lookup operation handle.
+ * The logic is such that after a proposal creation,
+ * it soon makes a proposal lookup in order to check
+ * if the merchant backend is actually aware.
+ */
+ struct TALER_MERCHANT_ProposalLookupOperation *plo;
+
+ /**
+ * The nonce.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey nonce;
+
+ /**
+ * URL of the merchant backend.
+ */
+ const char *merchant_url;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Merchant signature over the proposal.
+ */
+ struct TALER_MerchantSignatureP merchant_sig;
+
+ /**
+ * Merchant public key.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+};
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+proposal_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct ProposalState *ps = cls;
+#define MAKE_TRAIT_NONCE(ptr) \
+ TALER_TESTING_make_trait_merchant_pub (1, (struct \
+ TALER_MerchantPublicKeyP *) (ptr))
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_order_id (0, ps->order_id),
+ TALER_TESTING_make_trait_contract_terms (0, ps->contract_terms),
+ TALER_TESTING_make_trait_h_contract_terms (0, &ps->h_contract_terms),
+ TALER_TESTING_make_trait_merchant_sig (0, &ps->merchant_sig),
+ TALER_TESTING_make_trait_merchant_pub (0, &ps->merchant_pub),
+ MAKE_TRAIT_NONCE (&ps->nonce),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Used to fill the "proposal" CMD state with backend-provided
+ * values. Also double-checks that the proposal was correctly
+ * created.
+ *
+ * @param cls closure
+ * @param hr HTTP response we got
+ * @param sig merchant's signature
+ * @param hash hash over the contract
+ */
+static void
+proposal_lookup_initial_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const json_t *contract_terms,
+ const struct TALER_MerchantSignatureP *sig,
+ const struct GNUNET_HashCode *hash)
+{
+ struct ProposalState *ps = cls;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ const char *error_name;
+ unsigned int error_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ ps->plo = NULL;
+ if (ps->http_status != hr->http_status)
+ TALER_TESTING_FAIL (ps->is);
+
+ ps->contract_terms = json_deep_copy (contract_terms);
+ ps->h_contract_terms = *hash;
+ ps->merchant_sig = *sig;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract_terms,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ char *log;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u\n",
+ error_name,
+ error_line);
+ log = json_dumps (ps->contract_terms,
+ JSON_INDENT (1));
+ fprintf (stderr,
+ "%s\n",
+ log);
+ free (log);
+ TALER_TESTING_FAIL (ps->is);
+ }
+ ps->merchant_pub = merchant_pub;
+ TALER_TESTING_interpreter_next (ps->is);
+}
+
+
+/**
+ * Callback that processes the response following a
+ * proposal's put. NOTE: no contract terms are included
+ * here; they need to be taken via the "proposal lookup"
+ * method.
+ *
+ * @param cls closure.
+ * @param hr HTTP response
+ * @param order_id order id of the proposal.
+ */
+static void
+proposal_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const char *order_id)
+{
+ struct ProposalState *ps = cls;
+
+ ps->po = NULL;
+ if (ps->http_status != hr->http_status)
+ {
+ TALER_LOG_ERROR ("Given vs expected: %u(%d) vs %u\n",
+ hr->http_status,
+ (int) hr->ec,
+ ps->http_status);
+ TALER_TESTING_FAIL (ps->is);
+ }
+
+ if (0 == ps->http_status)
+ {
+ TALER_LOG_DEBUG ("/proposal, expected 0 status code\n");
+ TALER_TESTING_interpreter_next (ps->is);
+ return;
+ }
+
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ ps->order_id = GNUNET_strdup (order_id);
+ break;
+ default:
+ {
+ char *s = json_dumps (hr->reply,
+ JSON_COMPACT);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected status code from /proposal: %u (%d) at %s; JSON: %s\n",
+ hr->http_status,
+ hr->ec,
+ TALER_TESTING_interpreter_get_current_label (ps->is),
+ s);
+ GNUNET_free_non_null (s);
+ /**
+ * Not failing, as test cases are _supposed_
+ * to create non 200 OK situations.
+ */
+ TALER_TESTING_interpreter_next (ps->is);
+ }
+ return;
+ }
+
+ if (NULL ==
+ (ps->plo = TALER_MERCHANT_proposal_lookup (ps->is->ctx,
+ ps->merchant_url,
+ ps->order_id,
+ &ps->nonce,
+ &proposal_lookup_initial_cb,
+ ps)))
+ TALER_TESTING_FAIL (ps->is);
+}
+
+
+/**
+ * Run a "proposal" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+proposal_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ProposalState *ps = cls;
+ json_t *order;
+ json_error_t error;
+
+ ps->is = is;
+ order = json_loads (ps->order,
+ JSON_REJECT_DUPLICATES,
+ &error);
+ if (NULL == order)
+ {
+ // human error here.
+ GNUNET_break (0);
+ fprintf (stderr, "%s\n", error.text);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+
+ if (NULL == json_object_get (order,
+ "order_id"))
+ {
+ struct GNUNET_TIME_Absolute now;
+ char *order_id;
+
+ // FIXME: should probably use get_monotone() to ensure uniqueness!
+ now = GNUNET_TIME_absolute_get ();
+ order_id = GNUNET_STRINGS_data_to_string_alloc
+ (&now.abs_value_us,
+ sizeof (now.abs_value_us));
+ json_object_set_new (order,
+ "order_id",
+ json_string (order_id));
+ GNUNET_free (order_id);
+ }
+
+ GNUNET_CRYPTO_random_block
+ (GNUNET_CRYPTO_QUALITY_WEAK,
+ &ps->nonce,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+
+ ps->po = TALER_MERCHANT_order_put (is->ctx,
+ ps->merchant_url,
+ order,
+ &proposal_cb,
+ ps);
+ json_decref (order);
+ GNUNET_assert (NULL != ps->po);
+}
+
+
+/**
+ * Free the state of a "proposal" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+proposal_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ProposalState *ps = cls;
+
+ if (NULL != ps->po)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete (proposal put)\n",
+ cmd->label);
+ TALER_MERCHANT_proposal_cancel (ps->po);
+ ps->po = NULL;
+ }
+
+ if (NULL != ps->plo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete"
+ " (proposal lookup)\n",
+ cmd->label);
+ TALER_MERCHANT_proposal_lookup_cancel (ps->plo);
+ ps->plo = NULL;
+ }
+
+ json_decref (ps->contract_terms);
+ GNUNET_free_non_null ((void *) ps->order_id);
+ GNUNET_free (ps);
+}
+
+
+/**
+ * Make the "proposal" command.
+ *
+ * @param label command label
+ * @param merchant_url base URL of the merchant serving
+ * the proposal request.
+ * @param http_status expected HTTP status.
+ * @param order the order to PUT to the merchant.
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *order)
+{
+ struct ProposalState *ps;
+
+ ps = GNUNET_new (struct ProposalState);
+ ps->order = order;
+ ps->http_status = http_status;
+ ps->merchant_url = merchant_url;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &proposal_run,
+ .cleanup = &proposal_cleanup,
+ .traits = &proposal_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_proposal_lookup.c b/src/testing/testing_api_cmd_proposal_lookup.c
new file mode 100644
index 00000000..1ea0c0cb
--- /dev/null
+++ b/src/testing/testing_api_cmd_proposal_lookup.c
@@ -0,0 +1,311 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file exchange/testing_api_cmd_proposal_lookup.c
+ * @brief command to execute a proposal lookup
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * State for a "proposal lookup" CMD. Not used by
+ * the initial lookup operation.
+ */
+struct ProposalLookupState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * URL of the merchant backend.
+ */
+ const char *merchant_url;
+
+ /**
+ * Contract terms we downloaded. Only set if we got #MHD_HTTP_OK.
+ */
+ json_t *contract_terms;
+
+ /**
+ * Hash over the contract terms. Only set if we got #MHD_HTTP_OK.
+ */
+ struct GNUNET_HashCode contract_terms_hash;
+
+ /**
+ * Signature of the merchant. Only set if we got #MHD_HTTP_OK.
+ */
+ struct TALER_MerchantSignatureP merchant_sig;
+
+ /**
+ * Public key of the merchant. Only set if we got #MHD_HTTP_OK.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * /proposal/lookup operation handle.
+ */
+ struct TALER_MERCHANT_ProposalLookupOperation *plo;
+
+ /**
+ * Reference to a proposal operation. Will offer the
+ * nonce for the operation.
+ */
+ const char *proposal_reference;
+
+ /**
+ * Order id to lookup upon. If null, the @a proposal_reference
+ * will offer this value.
+ */
+ const char *order_id;
+};
+
+
+/**
+ * Free the state of a "proposal lookup" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+proposal_lookup_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ProposalLookupState *pls = cls;
+
+ if (NULL != pls->plo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete\n",
+ cmd->label);
+ TALER_MERCHANT_proposal_lookup_cancel (pls->plo);
+ pls->plo = NULL;
+ }
+ if (NULL != pls->contract_terms)
+ {
+ json_decref (pls->contract_terms);
+ pls->contract_terms = NULL;
+ }
+ GNUNET_free (pls);
+}
+
+
+/**
+ * Callback for "proposal lookup" operation, to check the
+ * response code is as expected.
+ *
+ * @param cls closure
+ * @param hr HTTP response we got
+ * @param contract_terms the contract terms; they are the
+ * backend-filled up proposal minus cryptographic
+ * information.
+ * @param sig merchant signature over the contract terms.
+ * @param hash hash code of the contract terms.
+ */
+static void
+proposal_lookup_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const json_t *contract_terms,
+ const struct TALER_MerchantSignatureP *sig,
+ const struct GNUNET_HashCode *hash)
+{
+ struct ProposalLookupState *pls = cls;
+
+ pls->plo = NULL;
+ if (pls->http_status != hr->http_status)
+ TALER_TESTING_FAIL (pls->is);
+ if (MHD_HTTP_OK == hr->http_status)
+ {
+ pls->contract_terms = json_object_get (hr->reply,
+ "contract_terms");
+ if (NULL == pls->contract_terms)
+ TALER_TESTING_FAIL (pls->is);
+ json_incref (pls->contract_terms);
+ pls->contract_terms_hash = *hash;
+ pls->merchant_sig = *sig;
+ {
+ const char *error_name;
+ unsigned int error_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &pls->merchant_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract_terms,
+ spec,
+ &error_name,
+ &error_line))
+ TALER_TESTING_FAIL (pls->is);
+ }
+ }
+ TALER_TESTING_interpreter_next (pls->is);
+}
+
+
+/**
+ * Run the "proposal lookup" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+proposal_lookup_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ProposalLookupState *pls = cls;
+ const char *order_id;
+ const struct TALER_MerchantPublicKeyP *nonce;
+ /* Only used if we do NOT use the nonce from traits. */
+ struct TALER_MerchantPublicKeyP dummy_nonce;
+ #define GET_TRAIT_NONCE(cmd,ptr) \
+ TALER_TESTING_get_trait_merchant_pub (cmd, 1, ptr)
+
+ pls->is = is;
+
+ if (NULL != pls->order_id)
+ {
+ order_id = pls->order_id;
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &dummy_nonce,
+ sizeof (dummy_nonce));
+ nonce = &dummy_nonce;
+ }
+ else
+ {
+ const struct TALER_TESTING_Command *proposal_cmd;
+
+ proposal_cmd = TALER_TESTING_interpreter_lookup_command
+ (is, pls->proposal_reference);
+
+ if (NULL == proposal_cmd)
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != GET_TRAIT_NONCE (proposal_cmd,
+ &nonce))
+ {
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &dummy_nonce,
+ sizeof (dummy_nonce));
+ nonce = &dummy_nonce;
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_order_id
+ (proposal_cmd, 0, &order_id))
+ TALER_TESTING_FAIL (is);
+ }
+ pls->plo = TALER_MERCHANT_proposal_lookup (is->ctx,
+ pls->merchant_url,
+ order_id,
+ &nonce->eddsa_pub,
+ &proposal_lookup_cb,
+ pls);
+ GNUNET_assert (NULL != pls->plo);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+proposal_lookup_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct ProposalLookupState *pls = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_contract_terms (0,
+ pls->contract_terms),
+ TALER_TESTING_make_trait_h_contract_terms (0,
+ &pls->contract_terms_hash),
+ TALER_TESTING_make_trait_merchant_sig (0,
+ &pls->merchant_sig),
+ TALER_TESTING_make_trait_merchant_pub (0,
+ &pls->merchant_pub),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Make a "proposal lookup" command.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant backend
+ * serving the proposal lookup request.
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference reference to a "proposal" CMD.
+ * @param order_id order id to lookup, can be NULL.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal_lookup
+ (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *order_id)
+{
+ struct ProposalLookupState *pls;
+
+ pls = GNUNET_new (struct ProposalLookupState);
+ pls->http_status = http_status;
+ pls->proposal_reference = proposal_reference;
+ pls->merchant_url = merchant_url;
+ pls->order_id = order_id;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pls,
+ .label = label,
+ .run = &proposal_lookup_run,
+ .cleanup = &proposal_lookup_cleanup,
+ .traits = &proposal_lookup_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_refund_increase.c b/src/testing/testing_api_cmd_refund_increase.c
new file mode 100644
index 00000000..da2c70b2
--- /dev/null
+++ b/src/testing/testing_api_cmd_refund_increase.c
@@ -0,0 +1,236 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2019 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_cmd_refund_increase.c
+ * @brief command to test refunds.
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a "refund increase" CMD.
+ */
+struct RefundIncreaseState
+{
+ /**
+ * Operation handle for a POST /refund request.
+ */
+ struct TALER_MERCHANT_RefundIncreaseOperation *rio;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Order id of the contract to refund.
+ */
+ const char *order_id;
+
+ /**
+ * The amount to refund.
+ */
+ const char *refund_amount;
+
+ /**
+ * Refund fee.
+ */
+ const char *refund_fee;
+
+ /**
+ * Human-readable justification for the refund.
+ */
+ const char *reason;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_code;
+};
+
+
+/**
+ * Free the state of a "refund increase" CMD, and
+ * possibly cancel a pending "refund increase" operation.
+ *
+ * @param cls closure
+ * @param cmd command currently being freed.
+ */
+static void
+refund_increase_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RefundIncreaseState *ris = cls;
+
+ if (NULL != ris->rio)
+ {
+ TALER_LOG_WARNING ("Refund-increase operation"
+ " did not complete\n");
+ TALER_MERCHANT_refund_increase_cancel (ris->rio);
+ }
+ GNUNET_free (ris);
+}
+
+
+/**
+ * Process POST /refund (increase) response; just checking
+ * if the HTTP response code is the one expected.
+ *
+ * @param cls closure
+ * @param hr HTTP response
+ */
+static void
+refund_increase_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct RefundIncreaseState *ris = cls;
+
+ ris->rio = NULL;
+ if (ris->http_code != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected status %u, got %u(%d) for refund increase\n",
+ ris->http_code,
+ hr->http_status,
+ (int) hr->ec);
+ TALER_TESTING_FAIL (ris->is);
+ }
+ TALER_TESTING_interpreter_next (ris->is);
+}
+
+
+/**
+ * Run the "refund increase" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is the interpreter state.
+ */
+static void
+refund_increase_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RefundIncreaseState *ris = cls;
+ struct TALER_Amount refund_amount;
+
+ ris->is = is;
+ if (GNUNET_OK != TALER_string_to_amount (ris->refund_amount,
+ &refund_amount))
+ TALER_TESTING_FAIL (is);
+ ris->rio = TALER_MERCHANT_refund_increase (is->ctx,
+ ris->merchant_url,
+ ris->order_id,
+ &refund_amount,
+ ris->reason,
+ &refund_increase_cb,
+ ris);
+ if (NULL == ris->rio)
+ TALER_TESTING_FAIL (is);
+}
+
+
+/**
+ * Offer internal data from the "refund increase" CMD
+ * state to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+refund_increase_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RefundIncreaseState *ris = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_string (0,
+ ris->refund_amount),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Define a "refund increase" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the backend serving the
+ * "refund increase" request.
+ * @param reason refund justification, human-readable.
+ * @param order_id order id of the contract to refund.
+ * @param refund_amount amount to be refund-increased.
+ * @param refund_fee refund fee.
+ * @param http_code expected HTTP response code.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_increase (const char *label,
+ const char *merchant_url,
+ const char *reason,
+ const char *order_id,
+ const char *refund_amount,
+ const char *refund_fee,
+ unsigned int http_code)
+{
+ struct RefundIncreaseState *ris;
+
+ ris = GNUNET_new (struct RefundIncreaseState);
+ ris->merchant_url = merchant_url;
+ ris->order_id = order_id;
+ ris->refund_amount = refund_amount;
+ ris->refund_fee = refund_fee;
+ ris->reason = reason;
+ ris->http_code = http_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ris,
+ .label = label,
+ .run = &refund_increase_run,
+ .cleanup = &refund_increase_cleanup,
+ .traits = &refund_increase_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_refund_increase.c */
diff --git a/src/testing/testing_api_cmd_refund_lookup.c b/src/testing/testing_api_cmd_refund_lookup.c
new file mode 100644
index 00000000..7db933a4
--- /dev/null
+++ b/src/testing/testing_api_cmd_refund_lookup.c
@@ -0,0 +1,457 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_refund_lookup.c
+ * @brief command to test refunds.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a "refund lookup" CMD.
+ */
+struct RefundLookupState
+{
+ /**
+ * Operation handle for a GET /public/refund request.
+ */
+ struct TALER_MERCHANT_RefundLookupOperation *rlo;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Order id to look up.
+ */
+ const char *order_id;
+
+ /**
+ * Reference to a "pay" CMD, used to double-check if
+ * refunded coins were actually spent:
+ */
+ const char *pay_reference;
+
+ /**
+ * Reference to a "refund increase" CMD that offer
+ * the expected amount to be refunded; can be NULL.
+ */
+ const char *increase_reference;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_code;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Explicit amount to be refunded, must be defined if @a
+ * increase_reference is NULL.
+ */
+ const char *refund_amount;
+};
+
+
+/**
+ * Free the state of a "refund lookup" CMD, and
+ * possibly cancel a pending "refund lookup" operation.
+ *
+ * @param cls closure
+ * @param cmd command currently being freed.
+ */
+static void
+refund_lookup_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RefundLookupState *rls = cls;
+
+ if (NULL != rls->rlo)
+ {
+ TALER_LOG_WARNING ("Refund-lookup operation"
+ " did not complete\n");
+ TALER_MERCHANT_refund_lookup_cancel (rls->rlo);
+ }
+ GNUNET_free (rls);
+}
+
+
+/**
+ * Process "GET /public/refund" (lookup) response;
+ * mainly checking if the refunded amount matches the
+ * expectation.
+ *
+ * @param cls closure
+ * @param hr HTTP response we got
+ * @param h_contract_terms hash of the contract terms to which the refund is applied
+ * @param merchant_pub public key of the merchant
+ * @param num_details length of the @a details array
+ * @param details details about the refund processing
+ */
+static void
+refund_lookup_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ unsigned int num_details,
+ const struct TALER_MERCHANT_RefundDetail *details)
+{
+ struct RefundLookupState *rls = cls;
+ struct GNUNET_CONTAINER_MultiHashMap *map;
+ const char *coin_reference;
+ const char *icoin_reference;
+ const char *refund_amount;
+ struct TALER_Amount acc;
+ struct TALER_Amount ra;
+
+ rls->rlo = NULL;
+ if (MHD_HTTP_GONE == rls->http_code)
+ {
+ /* special case: GONE is not the top-level code, but expected INSIDE the details */
+ if (MHD_HTTP_OK != hr->http_status)
+ TALER_TESTING_FAIL (rls->is);
+ for (unsigned int i = 0; i<num_details; i++)
+ if (MHD_HTTP_GONE != details[i].hr.http_status)
+ TALER_TESTING_FAIL (rls->is);
+ /* all good */
+ TALER_TESTING_interpreter_next (rls->is);
+ return;
+ }
+ if (rls->http_code != hr->http_status)
+ TALER_TESTING_FAIL (rls->is);
+ if (MHD_HTTP_OK != hr->http_status)
+ {
+ TALER_TESTING_interpreter_next (rls->is);
+ return;
+ }
+ map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+ /* Put in array every refunded coin. */
+ for (unsigned int i = 0; i<num_details; i++)
+ {
+ struct GNUNET_HashCode h_coin_pub;
+
+ if (MHD_HTTP_OK != details[i].hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Got unexpected status %u/%d for refunded coin %u\n",
+ details[i].hr.http_status,
+ (int) details[i].hr.ec,
+ i);
+ GNUNET_CONTAINER_multihashmap_destroy (map);
+ TALER_TESTING_FAIL (rls->is);
+ return;
+ }
+ TALER_LOG_DEBUG ("Coin %s refund is %s\n",
+ TALER_B2S (&details[i].coin_pub),
+ TALER_amount2s (&details[i].refund_amount));
+ GNUNET_CRYPTO_hash (&details[i].coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ &h_coin_pub);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (
+ map,
+ &h_coin_pub,
+ (void *) &details[i],
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ GNUNET_CONTAINER_multihashmap_destroy (map);
+ TALER_TESTING_FAIL (rls->is);
+ }
+ }
+
+ /* Compare spent coins with refunded, and if they match,
+ * increase an accumulator. */
+ {
+ const struct TALER_TESTING_Command *pay_cmd;
+
+ if (NULL == (pay_cmd = TALER_TESTING_interpreter_lookup_command (
+ rls->is,
+ rls->pay_reference)))
+ {
+ GNUNET_CONTAINER_multihashmap_destroy (map);
+ TALER_TESTING_FAIL (rls->is);
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_reference (
+ pay_cmd,
+ 0,
+ &coin_reference))
+ {
+ GNUNET_CONTAINER_multihashmap_destroy (map);
+ TALER_TESTING_FAIL (rls->is);
+ }
+ }
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero ("EUR",
+ &acc));
+ {
+ char *coin_reference_dup;
+
+ coin_reference_dup = GNUNET_strdup (coin_reference);
+ for (icoin_reference = strtok (coin_reference_dup, ";");
+ NULL != icoin_reference;
+ icoin_reference = strtok (NULL, ";"))
+ {
+ const struct TALER_CoinSpendPrivateKeyP *icoin_priv;
+ struct TALER_CoinSpendPublicKeyP icoin_pub;
+ struct GNUNET_HashCode h_icoin_pub;
+ const struct TALER_MERCHANT_RefundDetail *idetail;
+ const struct TALER_TESTING_Command *icoin_cmd;
+
+ if (NULL ==
+ (icoin_cmd =
+ TALER_TESTING_interpreter_lookup_command (rls->is,
+ icoin_reference)) )
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Bad reference `%s'\n",
+ icoin_reference);
+ TALER_TESTING_interpreter_fail (rls->is);
+ GNUNET_CONTAINER_multihashmap_destroy (map);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_priv (icoin_cmd,
+ 0,
+ &icoin_priv))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR ("Command `%s' failed to give coin priv trait\n",
+ icoin_reference);
+ TALER_TESTING_interpreter_fail (rls->is);
+ GNUNET_CONTAINER_multihashmap_destroy (map);
+ return;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&icoin_priv->eddsa_priv,
+ &icoin_pub.eddsa_pub);
+ TALER_LOG_DEBUG ("Looking at coin %s\n",
+ TALER_B2S (&icoin_pub));
+ GNUNET_CRYPTO_hash (&icoin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ &h_icoin_pub);
+
+ idetail = GNUNET_CONTAINER_multihashmap_get (map,
+ &h_icoin_pub);
+
+ /* Can be NULL: not all coins are involved in refund */
+ if (NULL == idetail)
+ continue;
+ TALER_LOG_DEBUG ("Found coin %s refund of %s\n",
+ TALER_B2S (&idetail->coin_pub),
+ TALER_amount2s (&idetail->refund_amount));
+ GNUNET_assert (0 <=
+ TALER_amount_add (&acc,
+ &acc,
+ &idetail->refund_amount));
+ }
+ GNUNET_free (coin_reference_dup);
+ }
+
+
+ {
+ const struct TALER_TESTING_Command *increase_cmd;
+
+ if (NULL !=
+ (increase_cmd
+ = TALER_TESTING_interpreter_lookup_command (rls->is,
+ rls->increase_reference)))
+ {
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_string (increase_cmd,
+ 0,
+ &refund_amount))
+ TALER_TESTING_FAIL (rls->is);
+
+ if (GNUNET_OK !=
+ TALER_string_to_amount (refund_amount,
+ &ra))
+ TALER_TESTING_FAIL (rls->is);
+ }
+ else
+ {
+ GNUNET_assert (NULL != rls->refund_amount);
+
+ if (GNUNET_OK !=
+ TALER_string_to_amount (rls->refund_amount,
+ &ra))
+ TALER_TESTING_FAIL (rls->is);
+ }
+ }
+ GNUNET_CONTAINER_multihashmap_destroy (map);
+
+ /* Check that what the backend claims to have been refunded
+ * actually matches _our_ refund expectation. */
+ if (0 != TALER_amount_cmp (&acc,
+ &ra))
+ {
+ char *a1;
+
+ a1 = TALER_amount_to_string (&ra);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Incomplete refund: expected '%s', got '%s'\n",
+ a1,
+ TALER_amount2s (&acc));
+ GNUNET_free (a1);
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+
+ TALER_TESTING_interpreter_next (rls->is);
+}
+
+
+/**
+ * Run the "refund lookup" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being currently run.
+ * @param is interpreter state.
+ */
+static void
+refund_lookup_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RefundLookupState *rls = cls;
+
+ rls->is = is;
+ rls->rlo = TALER_MERCHANT_refund_lookup (is->ctx,
+ rls->merchant_url,
+ rls->order_id,
+ &refund_lookup_cb,
+ rls);
+ GNUNET_assert (NULL != rls->rlo);
+}
+
+
+/**
+ * Define a "refund lookup" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * "refund lookup" request.
+ * @param increase_reference reference to a "refund increase" CMD
+ * that will offer the amount to check the looked up refund
+ * against. Must NOT be NULL.
+ * @param pay_reference reference to the "pay" CMD whose coins got
+ * refunded. It is used to double-check if the refunded
+ * coins were actually spent in the first place.
+ * @param order_id order id whose refund status is to be looked up.
+ * @param http_code expected HTTP response code.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_lookup (
+ const char *label,
+ const char *merchant_url,
+ const char *increase_reference,
+ const char *pay_reference,
+ const char *order_id,
+ unsigned int http_code)
+{
+ struct RefundLookupState *rls;
+
+ rls = GNUNET_new (struct RefundLookupState);
+ rls->merchant_url = merchant_url;
+ rls->order_id = order_id;
+ rls->pay_reference = pay_reference;
+ rls->increase_reference = increase_reference;
+ rls->http_code = http_code;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = rls,
+ .label = label,
+ .run = &refund_lookup_run,
+ .cleanup = &refund_lookup_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Define a "refund lookup" CMD, equipped with a expected refund
+ * amount.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * "refund lookup" request.
+ * @param increase_reference reference to a "refund increase" CMD
+ * that will offer the amount to check the looked up refund
+ * against. Can be NULL, takes precedence over @a
+ * refund_amount.
+ * @param pay_reference reference to the "pay" CMD whose coins got
+ * refunded. It is used to double-check if the refunded
+ * coins were actually spent in the first place.
+ * @param order_id order id whose refund status is to be looked up.
+ * @param http_code expected HTTP response code.
+ * @param refund_amount expected refund amount. Must be defined
+ * if @a increase_reference is NULL.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_lookup_with_amount (
+ const char *label,
+ const char *merchant_url,
+ const char *increase_reference,
+ const char *pay_reference,
+ const char *order_id,
+ unsigned int http_code,
+ const char *refund_amount)
+{
+ struct RefundLookupState *rls;
+
+ rls = GNUNET_new (struct RefundLookupState);
+ rls->merchant_url = merchant_url;
+ rls->order_id = order_id;
+ rls->pay_reference = pay_reference;
+ rls->increase_reference = increase_reference;
+ rls->http_code = http_code;
+ rls->refund_amount = refund_amount;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = rls,
+ .label = label,
+ .run = &refund_lookup_run,
+ .cleanup = &refund_lookup_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_refund_lookup.c */
diff --git a/src/testing/testing_api_cmd_rewind.c b/src/testing/testing_api_cmd_rewind.c
new file mode 100644
index 00000000..2e43b75f
--- /dev/null
+++ b/src/testing/testing_api_cmd_rewind.c
@@ -0,0 +1,120 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_rewind.c
+ * @brief command to rewind the instruction pointer.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a "rewind" CMD.
+ */
+struct RewindIpState
+{
+ /**
+ * Instruction pointer to set into the interpreter.
+ */
+ unsigned int new_ip;
+
+ /**
+ * How many times this set should take place.
+ * However, this value lives at the calling process,
+ * and this CMD is only in charge of checking and
+ * decremeting it.
+ */
+ unsigned int *counter;
+};
+
+
+/**
+ * Only defined to respect the API.
+ */
+static void
+rewind_ip_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+}
+
+
+/**
+ * Run the "rewind" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being executed now.
+ * @param is the interpreter state.
+ */
+static void
+rewind_ip_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RewindIpState *ris = cls;
+
+ if (1 < *ris->counter)
+ {
+ is->ip = ris->new_ip;
+ *ris->counter -= 1;
+ }
+
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Make the instruction pointer point to @a new_ip
+ * only if @a counter is greater than zero.
+ *
+ * @param label command label
+ * @param new_ip new instruction pointer's value. Note that,
+ * when the next instruction will be called, the interpreter
+ * will increment the ip _anyway_ so this value must be
+ * set to the index of the instruction we want to execute next
+ * MINUS one.
+ * @param counter counts how many times the rewinding has
+ * to happen.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_rewind_ip
+ (const char *label,
+ int new_ip,
+ unsigned int *counter)
+{
+ struct RewindIpState *ris;
+
+ ris = GNUNET_new (struct RewindIpState);
+ ris->new_ip = new_ip;
+ ris->counter = counter;
+
+ struct TALER_TESTING_Command cmd = {
+ .cls = ris,
+ .label = label,
+ .run = &rewind_ip_run,
+ .cleanup = &rewind_ip_cleanup
+ };
+
+ return cmd;
+}
diff --git a/src/testing/testing_api_cmd_tip_authorize.c b/src/testing/testing_api_cmd_tip_authorize.c
new file mode 100644
index 00000000..928ec04d
--- /dev/null
+++ b/src/testing/testing_api_cmd_tip_authorize.c
@@ -0,0 +1,369 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_tip_authorize.c
+ * @brief command to test the tipping.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a /tip-authorize CMD.
+ */
+struct TipAuthorizeState
+{
+
+ /**
+ * Merchant base URL.
+ */
+ const char *merchant_url;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Human-readable justification for the
+ * tip authorization carried on by this CMD.
+ */
+ const char *justification;
+
+ /**
+ * Amount that should be authorized for tipping.
+ */
+ const char *amount;
+
+ /**
+ * Expected Taler error code for this CMD.
+ */
+ enum TALER_ErrorCode expected_ec;
+
+ /**
+ * Tip taler:// URI.
+ */
+ const char *tip_uri;
+
+ /**
+ * The tip id; set when the CMD succeeds.
+ */
+ struct GNUNET_HashCode tip_id;
+
+ /**
+ * Expiration date for this tip.
+ */
+ struct GNUNET_TIME_Absolute tip_expiration;
+
+ /**
+ * Handle to the on-going /tip-authorize request.
+ */
+ struct TALER_MERCHANT_TipAuthorizeOperation *tao;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Callback for a /tip-authorize request. Set into the state
+ * what was returned from the backend (@a tip_id and @a
+ * tip_expiration).
+ *
+ * @param cls closure
+ * @param hr HTTP response we got
+ * @param taler_tip_uri URI to let the wallet know about the tip
+ * @param tip_id unique identifier for the tip
+ */
+static void
+tip_authorize_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ struct GNUNET_HashCode *tip_id,
+ const char *taler_tip_uri)
+{
+ struct TipAuthorizeState *tas = cls;
+
+ tas->tao = NULL;
+ if (tas->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ hr->ec,
+ TALER_TESTING_interpreter_get_current_label (tas->is));
+ TALER_TESTING_interpreter_fail (tas->is);
+ return;
+ }
+
+ if (tas->expected_ec != hr->ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error code %d (%u) to command %s\n",
+ (int) hr->ec,
+ hr->http_status,
+ TALER_TESTING_interpreter_get_current_label (tas->is));
+ TALER_TESTING_interpreter_fail (tas->is);
+ return;
+ }
+ if ( (MHD_HTTP_OK == hr->http_status) &&
+ (TALER_EC_NONE == hr->ec) )
+ {
+ tas->tip_uri = strdup (taler_tip_uri);
+ tas->tip_id = *tip_id;
+ }
+ TALER_TESTING_interpreter_next (tas->is);
+}
+
+
+/**
+ * Offers information from the /tip-authorize CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+tip_authorize_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct TipAuthorizeState *tas = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_tip_id (0, &tas->tip_id),
+ TALER_TESTING_trait_end (),
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Runs the /tip-authorize CMD
+ *
+ * @param cls closure
+ * @param cmd the CMD representing _this_ command
+ * @param is interpreter state
+ */
+static void
+tip_authorize_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TipAuthorizeState *tas = cls;
+ struct TALER_Amount amount;
+
+ tas->is = is;
+ if (GNUNET_OK != TALER_string_to_amount (tas->amount,
+ &amount))
+ TALER_TESTING_FAIL (is);
+
+ tas->tao = TALER_MERCHANT_tip_authorize (is->ctx,
+ tas->merchant_url,
+ "http://merchant.com/pickup",
+ "http://merchant.com/continue",
+ &amount,
+ tas->justification,
+ tip_authorize_cb,
+ tas);
+
+ GNUNET_assert (NULL != tas->tao);
+}
+
+
+/**
+ * Run the /tip-authorize CMD, the "fake" version of it.
+ *
+ * @param cls closure
+ * @param cmd the CMD representing _this_ command
+ * @param is interpreter state *
+ */
+static void
+tip_authorize_fake_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TipAuthorizeState *tas = cls;
+
+ /* Make up a tip id. */
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &tas->tip_id,
+ sizeof (struct GNUNET_HashCode));
+
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Free the state from a /tip-authorize CMD, and possibly
+ * cancel any pending operation.
+ *
+ * @param cls closure
+ * @param cmd the /tip-authorize CMD that is about to be freed.
+ */
+static void
+tip_authorize_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct TipAuthorizeState *tas = cls;
+
+ if (NULL != tas->tao)
+ {
+ TALER_LOG_WARNING ("Tip-autorize operation"
+ " did not complete\n");
+ TALER_MERCHANT_tip_authorize_cancel (tas->tao);
+ }
+ GNUNET_free (tas);
+}
+
+
+/**
+ * Create a /tip-authorize CMD, specifying the Taler error code
+ * that is expected to be returned by the backend.
+ *
+ * @param label this command label
+ * @param merchant_url the base URL of the merchant that will
+ * serve the /tip-authorize request.
+ * @param exchange_url the base URL of the exchange that owns
+ * the reserve from which the tip is going to be gotten.
+ * @param http_status the HTTP response code which is expected
+ * for this operation.
+ * @param justification human-readable justification for this
+ * tip authorization.
+ * @param amount the amount to authorize for tipping.
+ * @param ec expected Taler-defined error code.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize_with_ec (const char *label,
+ const char *merchant_url,
+ const char *exchange_url,
+ unsigned int http_status,
+ const char *justification,
+ const char *amount,
+ enum TALER_ErrorCode ec)
+{
+ struct TipAuthorizeState *tas;
+
+ tas = GNUNET_new (struct TipAuthorizeState);
+ tas->merchant_url = merchant_url;
+ tas->justification = justification;
+ tas->amount = amount;
+ tas->http_status = http_status;
+ tas->expected_ec = ec;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .cls = tas,
+ .run = &tip_authorize_run,
+ .cleanup = &tip_authorize_cleanup,
+ .traits = &tip_authorize_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Create a /tip-authorize CMD.
+ *
+ * @param label this command label
+ * @param merchant_url the base URL of the merchant that will
+ * serve the /tip-authorize request.
+ * @param exchange_url the base URL of the exchange that owns
+ * the reserve from which the tip is going to be gotten.
+ * @param http_status the HTTP response code which is expected
+ * for this operation.
+ * @param justification human-readable justification for this
+ * tip authorization.
+ * @param amount the amount to authorize for tipping.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize (const char *label,
+ const char *merchant_url,
+ const char *exchange_url,
+ unsigned int http_status,
+ const char *justification,
+ const char *amount)
+{
+ struct TipAuthorizeState *tas;
+
+ tas = GNUNET_new (struct TipAuthorizeState);
+ tas->merchant_url = merchant_url;
+ tas->justification = justification;
+ tas->amount = amount;
+ tas->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .cls = tas,
+ .run = &tip_authorize_run,
+ .cleanup = &tip_authorize_cleanup,
+ .traits = &tip_authorize_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * This commands does not query the backend at all,
+ * but just makes up a fake authorization id that will
+ * be subsequently used by the "pick up" CMD in order
+ * to test against such a case.
+ *
+ * @param label command label.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize_fake (const char *label)
+{
+ struct TipAuthorizeState *tas;
+
+ tas = GNUNET_new (struct TipAuthorizeState);
+ {
+ struct TALER_TESTING_Command cmd = {
+ .label = label,
+ .cls = tas,
+ .run = &tip_authorize_fake_run,
+ .cleanup = &tip_authorize_cleanup,
+ .traits = &tip_authorize_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_tip_authorize.c */
diff --git a/src/testing/testing_api_cmd_tip_pickup.c b/src/testing/testing_api_cmd_tip_pickup.c
new file mode 100644
index 00000000..dd963520
--- /dev/null
+++ b/src/testing/testing_api_cmd_tip_pickup.c
@@ -0,0 +1,437 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_tip.c
+ * @brief command to test the tipping.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * State for a /tip-pickup CMD.
+ */
+struct TipPickupState
+{
+ /**
+ * Merchant base URL.
+ */
+ const char *merchant_url;
+
+ /**
+ * Exchange base URL.
+ */
+ const char *exchange_url;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Reference to a /tip/authorize CMD. This will be used to
+ * get the tip id to make the request with.
+ */
+ const char *authorize_reference;
+
+ /**
+ * If set to non NULL, it references another pickup CMD
+ * that will provide all the data which is needed to issue
+ * the request (like planchet secrets, denomination keys..).
+ */
+ const char *replay_reference;
+
+ /**
+ * Handle to a on-going /tip/pickup request.
+ */
+ struct TALER_MERCHANT_TipPickupOperation *tpo;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * An array of string-defined amounts that indicates
+ * which denominations are going to be used to receive
+ * tips.
+ */
+ const char **amounts;
+
+ /**
+ * The object version of the above @a amounts.
+ */
+ struct TALER_Amount *amounts_obj;
+
+ /**
+ * How many coins are involved in the tipping operation.
+ */
+ unsigned int num_coins;
+
+ /**
+ * The array of denomination keys, in the same order of @a
+ * amounts.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey **dks;
+
+ /**
+ * The array of planchet secrets, in the same order of @a
+ * amounts.
+ */
+ struct TALER_PlanchetSecretsP *psa;
+
+ /**
+ * Set (by the interpreter) to an array of @a num_coins
+ * signatures created from the (successful) tip operation.
+ */
+ struct TALER_DenominationSignature *sigs;
+
+ /**
+ * Expected Taler error code (NOTE: this is NOT the HTTP
+ * response code).
+ */
+ enum TALER_ErrorCode expected_ec;
+};
+
+
+/**
+ * Callback for a /tip-pickup request, it mainly checks if
+ * values returned from the backend are as expected, and if so
+ * (and if the status was 200 OK) proceede with the withdrawal.
+ *
+ * @param cls closure
+ * @param hr HTTP response
+ * @param num_sigs length of the @a sigs array,
+ * 0 on error
+ * @param sigs array of signatures over the coins, NULL on error
+ */
+static void
+pickup_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ unsigned int num_sigs,
+ const struct TALER_DenominationSignature *sigs)
+{
+ struct TipPickupState *tps = cls;
+
+ tps->tpo = NULL;
+ if (hr->http_status != tps->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (tps->is));
+ TALER_TESTING_FAIL (tps->is);
+ }
+
+ if (hr->ec != tps->expected_ec)
+ TALER_TESTING_FAIL (tps->is);
+
+ /* Safe to go ahead: http status was expected. */
+ if ( (MHD_HTTP_OK != hr->http_status) ||
+ (TALER_EC_NONE != hr->ec) )
+ {
+ TALER_TESTING_interpreter_next (tps->is);
+ return;
+ }
+ if (num_sigs != tps->num_coins)
+ TALER_TESTING_FAIL (tps->is);
+ tps->sigs = GNUNET_new_array (tps->num_coins,
+ struct TALER_DenominationSignature);
+ for (unsigned int i = 0; i<num_sigs; i++)
+ tps->sigs[i].rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_dup (sigs[i].rsa_signature);
+ TALER_TESTING_interpreter_next (tps->is);
+}
+
+
+/**
+ * Run a /tip-pickup CMD.
+ *
+ * @param cls closure
+ * @param cmd the current /tip-pickup CMD.
+ * @param is interpreter state.
+ */
+static void
+tip_pickup_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TipPickupState *tps = cls;
+ unsigned int num_planchets;
+ const struct TALER_TESTING_Command *replay_cmd;
+ const struct TALER_TESTING_Command *authorize_cmd;
+ const struct GNUNET_HashCode *tip_id;
+
+ tps->is = is;
+ tps->exchange_url = TALER_EXCHANGE_get_base_url (is->exchange);
+ if (NULL == tps->replay_reference)
+ {
+ replay_cmd = NULL;
+
+ /* Count planchets. */
+ for (num_planchets = 0;
+ NULL != tps->amounts[num_planchets];
+ num_planchets++)
+ ;
+ }
+ else
+ {
+ const unsigned int *np;
+
+ if (NULL == /* looking for "parent" tip-pickup command */
+ (replay_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ tps->replay_reference)) )
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_uint (replay_cmd,
+ 0,
+ &np))
+ TALER_TESTING_FAIL (is);
+ num_planchets = *np;
+ }
+
+ if (NULL ==
+ (authorize_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ tps->authorize_reference)) )
+ TALER_TESTING_FAIL (is);
+
+ tps->num_coins = num_planchets;
+ {
+ struct TALER_MERCHANT_PlanchetData planchets[num_planchets];
+
+ tps->psa = GNUNET_new_array (num_planchets,
+ struct TALER_PlanchetSecretsP);
+ tps->dks = GNUNET_new_array (num_planchets,
+ const struct TALER_EXCHANGE_DenomPublicKey *);
+ tps->amounts_obj = GNUNET_new_array (num_planchets,
+ struct TALER_Amount);
+ for (unsigned int i = 0; i<num_planchets; i++)
+ {
+ if (NULL == replay_cmd)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (tps->amounts[i],
+ &tps->amounts_obj[i]));
+ tps->dks[i] = TALER_TESTING_find_pk (is->keys,
+ &tps->amounts_obj[i]);
+ if (NULL == tps->dks[i])
+ TALER_TESTING_FAIL (is);
+ TALER_planchet_setup_random (&tps->psa[i]);
+ }
+ else
+ {
+ struct TALER_PlanchetSecretsP *ps;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (replay_cmd,
+ i,
+ &tps->dks[i]))
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_planchet_secrets (replay_cmd,
+ i,
+ &ps))
+ TALER_TESTING_FAIL (is);
+ tps->psa[i] = *ps;
+ }
+ planchets[i].pk = tps->dks[i];
+ planchets[i].ps = tps->psa[i];
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_tip_id (authorize_cmd,
+ 0,
+ &tip_id))
+ TALER_TESTING_FAIL (is);
+
+ tps->tpo = TALER_MERCHANT_tip_pickup (is->ctx,
+ tps->merchant_url,
+ tip_id,
+ num_planchets,
+ planchets,
+ &pickup_cb,
+ tps);
+ GNUNET_assert (NULL != tps->tpo);
+ }
+}
+
+
+/**
+ * Free a /tip-pickup CMD state, and possibly cancel a
+ * pending /tip-pickup request.
+ *
+ * @param cls closure.
+ * @param cmd current CMD to be freed.
+ */
+static void
+tip_pickup_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct TipPickupState *tps = cls;
+
+ GNUNET_free_non_null (tps->amounts_obj);
+ GNUNET_free_non_null (tps->dks);
+ GNUNET_free_non_null (tps->psa);
+ if (NULL != tps->sigs)
+ {
+ for (unsigned int i = 0; i<tps->num_coins; i++)
+ if (NULL != tps->sigs[i].rsa_signature)
+ GNUNET_CRYPTO_rsa_signature_free (tps->sigs[i].rsa_signature);
+ GNUNET_free (tps->sigs);
+ }
+ if (NULL != tps->tpo)
+ {
+ TALER_LOG_WARNING ("Tip-pickup operation did not complete\n");
+ TALER_MERCHANT_tip_pickup_cancel (tps->tpo);
+ }
+ GNUNET_free (tps);
+}
+
+
+/**
+ * Offers information from the /tip-pickup CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+tip_pickup_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct TipPickupState *tps = cls;
+ #define NUM_TRAITS (tps->num_coins * 5) + 2
+ struct TALER_TESTING_Trait traits[NUM_TRAITS];
+
+ for (unsigned int i = 0; i<tps->num_coins; i++)
+ {
+ traits[i] = TALER_TESTING_make_trait_planchet_secrets (i,
+ &tps->psa[i]);
+ traits[i + tps->num_coins] =
+ TALER_TESTING_make_trait_coin_priv (i, &tps->psa[i].coin_priv);
+ traits[i + (tps->num_coins * 2)] =
+ TALER_TESTING_make_trait_denom_pub (i, tps->dks[i]);
+ traits[i + (tps->num_coins * 3)] =
+ TALER_TESTING_make_trait_denom_sig (i, &tps->sigs[i]);
+ traits[i + (tps->num_coins * 4)] =
+ TALER_TESTING_make_trait_amount_obj (i, &tps->amounts_obj[i]);
+ }
+ traits[NUM_TRAITS - 2]
+ = TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL,
+ tps->exchange_url);
+ traits[NUM_TRAITS - 1] = TALER_TESTING_trait_end ();
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Define a /tip-pickup CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the backend which will serve
+ * the /tip-pickup request.
+ * @param http_status expected HTTP response code.
+ * @param authorize_reference reference to a /tip-autorize CMD
+ * that offers a tip id to pick up.
+ * @param amounts array of string-defined amounts that specifies
+ * which denominations will be accepted for tipping.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_pickup (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *authorize_reference,
+ const char **amounts)
+{
+ struct TipPickupState *tps;
+
+ tps = GNUNET_new (struct TipPickupState);
+ tps->merchant_url = merchant_url;
+ tps->authorize_reference = authorize_reference;
+ tps->amounts = amounts;
+ tps->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tps,
+ .label = label,
+ .run = &tip_pickup_run,
+ .cleanup = &tip_pickup_cleanup,
+ .traits = &tip_pickup_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Define a /tip-pickup CMD, equipped with the expected error
+ * code.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the backend which will serve
+ * the /tip-pickup request.
+ * @param http_status expected HTTP response code.
+ * @param authorize_reference reference to a /tip-autorize CMD
+ * that offers a tip id to pick up.
+ * @param amounts array of string-defined amounts that specifies
+ * which denominations will be accepted for tipping.
+ * @param exchange connection handle to the exchange that will
+ * eventually serve the withdraw operation.
+ * @param ec expected Taler error code.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_pickup_with_ec (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *authorize_reference,
+ const char **amounts,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_TESTING_Command cmd;
+ struct TipPickupState *tps;
+
+ cmd = TALER_TESTING_cmd_tip_pickup (label,
+ merchant_url,
+ http_status,
+ authorize_reference,
+ amounts);
+ tps = cmd.cls;
+ tps->expected_ec = ec;
+ return cmd;
+}
+
+
+/* end of testing_api_cmd_tip_pickup.c */
diff --git a/src/testing/testing_api_cmd_tip_query.c b/src/testing/testing_api_cmd_tip_query.c
new file mode 100644
index 00000000..70e59c12
--- /dev/null
+++ b/src/testing/testing_api_cmd_tip_query.c
@@ -0,0 +1,297 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_tip_query.c
+ * @brief command to test the tipping.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a /tip-query CMD.
+ */
+struct TipQueryState
+{
+
+ /**
+ * The merchant base URL.
+ */
+ const char *merchant_url;
+
+ /**
+ * Expected HTTP response code for this CMD.
+ */
+ unsigned int http_status;
+
+ /**
+ * The handle to the current /tip-query request.
+ */
+ struct TALER_MERCHANT_TipQueryOperation *tqo;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Expected amount to be picked up.
+ */
+ const char *expected_amount_picked_up;
+
+ /**
+ * Expected amount to be tip-authorized.
+ */
+ const char *expected_amount_authorized;
+
+ /**
+ * Amount that is expected to be still available
+ * from the tip reserve.
+ */
+ const char *expected_amount_available;
+};
+
+
+/**
+ * Callback to process a GET /tip-query request, it mainly
+ * checks that what the backend returned matches the command's
+ * expectations.
+ *
+ * @param cls closure
+ * @param hr HTTP response
+ * @param reserve_expiration when the tip reserve will expire
+ * @param reserve_pub tip reserve public key
+ * @param amount_authorized total amount authorized on tip reserve
+ * @param amount_available total amount still available on
+ * tip reserve
+ * @param amount_picked_up total amount picked up from tip reserve
+ */
+static void
+tip_query_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ struct GNUNET_TIME_Absolute reserve_expiration,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *amount_authorized,
+ struct TALER_Amount *amount_available,
+ struct TALER_Amount *amount_picked_up)
+{
+ struct TipQueryState *tqs = cls;
+ struct TALER_Amount a;
+
+ tqs->tqo = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Tip query callback at command `%s'\n",
+ TALER_TESTING_interpreter_get_current_label (tqs->is));
+ GNUNET_assert (NULL != reserve_pub);
+ GNUNET_assert (NULL != amount_authorized);
+ GNUNET_assert (NULL != amount_available);
+ GNUNET_assert (NULL != amount_picked_up);
+
+ if (tqs->expected_amount_available)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (tqs->expected_amount_available,
+ &a));
+ {
+ char *str;
+
+ str = TALER_amount_to_string (amount_available);
+ TALER_LOG_INFO ("expected available %s, actual %s\n",
+ TALER_amount2s (&a),
+ str);
+ GNUNET_free (str);
+ }
+ if (0 !=
+ TALER_amount_cmp (amount_available,
+ &a))
+ TALER_TESTING_FAIL (tqs->is);
+ }
+
+ if (tqs->expected_amount_authorized)
+ {
+ char *str;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (tqs->expected_amount_authorized,
+ &a));
+ str = TALER_amount_to_string (amount_authorized);
+ TALER_LOG_INFO ("expected authorized %s, actual %s\n",
+ TALER_amount2s (&a),
+ str);
+ GNUNET_free (str);
+ if (0 !=
+ TALER_amount_cmp (amount_authorized,
+ &a))
+ TALER_TESTING_FAIL (tqs->is);
+ }
+
+ if (tqs->expected_amount_picked_up)
+ {
+ char *str;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (tqs->expected_amount_picked_up,
+ &a));
+ str = TALER_amount_to_string (amount_picked_up);
+ TALER_LOG_INFO ("expected picked_up %s, actual %s\n",
+ TALER_amount2s (&a),
+ str);
+ GNUNET_free (str);
+ if (0 !=
+ TALER_amount_cmp (amount_picked_up,
+ &a))
+ TALER_TESTING_FAIL (tqs->is);
+ }
+
+ if (tqs->http_status != hr->http_status)
+ TALER_TESTING_FAIL (tqs->is);
+ TALER_TESTING_interpreter_next (tqs->is);
+}
+
+
+/**
+ * Free the state from a /tip-query CMD, and possibly cancel
+ * a pending /tip-query request.
+ *
+ * @param cls closure.
+ * @param cmd the /tip-query CMD to free.
+ */
+static void
+tip_query_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct TipQueryState *tqs = cls;
+
+ if (NULL != tqs->tqo)
+ {
+ TALER_LOG_WARNING ("Tip-query operation"
+ " did not complete\n");
+ TALER_MERCHANT_tip_query_cancel (tqs->tqo);
+ }
+ GNUNET_free (tqs);
+}
+
+
+/**
+ * Run a /tip-query CMD.
+ *
+ * @param cls closure.
+ * @param cmd the current /tip-query CMD.
+ * @param is the interpreter state.
+ */
+static void
+tip_query_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TipQueryState *tqs = cls;
+
+ tqs->is = is;
+ tqs->tqo = TALER_MERCHANT_tip_query (is->ctx,
+ tqs->merchant_url,
+ &tip_query_cb,
+ tqs);
+ GNUNET_assert (NULL != tqs->tqo);
+}
+
+
+/**
+ * Define a /tip-query CMD equipped with a expected amount.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ * server the /tip-query request.
+ * @param http_status expected HTTP response code for the
+ * /tip-query request.
+ * @param expected_amount_picked_up expected amount already
+ * picked up.
+ * @param expected_amount_authorized expected amount that was
+ * authorized in the first place.
+ * @param expected_amount_available expected amount which is
+ * still available from the tip reserve
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_query_with_amounts (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *expected_amount_picked_up,
+ const char *expected_amount_authorized,
+ const char *expected_amount_available)
+{
+ struct TipQueryState *tqs;
+
+ tqs = GNUNET_new (struct TipQueryState);
+ tqs->merchant_url = merchant_url;
+ tqs->http_status = http_status;
+ tqs->expected_amount_picked_up = expected_amount_picked_up;
+ tqs->expected_amount_authorized = expected_amount_authorized;
+ tqs->expected_amount_available = expected_amount_available;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tqs,
+ .label = label,
+ .run = &tip_query_run,
+ .cleanup = &tip_query_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Define a /tip-query CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ * server the /tip-query request.
+ * @param http_status expected HTTP response code for the
+ * /tip-query request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_query (const char *label,
+ const char *merchant_url,
+ unsigned int http_status)
+{
+ struct TipQueryState *tqs;
+
+ tqs = GNUNET_new (struct TipQueryState);
+ tqs->merchant_url = merchant_url;
+ tqs->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tqs,
+ .label = label,
+ .run = &tip_query_run,
+ .cleanup = &tip_query_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_tip_query.c */
diff --git a/src/testing/testing_api_cmd_track_transaction.c b/src/testing/testing_api_cmd_track_transaction.c
new file mode 100644
index 00000000..c9450500
--- /dev/null
+++ b/src/testing/testing_api_cmd_track_transaction.c
@@ -0,0 +1,295 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_track_transaction.c
+ * @brief command to test /track/transaction
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a "track transaction" CMD.
+ */
+struct TrackTransactionState
+{
+ /**
+ * Handle for a pending /track/transaction request.
+ */
+ struct TALER_MERCHANT_TrackTransactionHandle *tth;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Reference to a "pay" CMD, used to get the order
+ * id to issue the track against.
+ */
+ const char *pay_reference;
+
+ /**
+ * Subject line of the wire transfer that paid
+ * the tracked contract back. WARNING: impredictible
+ * behaviour if _multiple_ wire transfers were
+ * issued to pay this contract back.
+ */
+ const char *wtid_str;
+
+ /**
+ * Binary form of @a wtid_str, expected by other commands
+ * in this form.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * base URL of the exchange that issued (or was supposed to,
+ * in case 202 Accepted was returned) the wire transfer to
+ * pay the tracked contract back.
+ */
+ const char *exchange_url;
+
+};
+
+
+/**
+ * Function called with detailed wire transfer data; checks
+ * if HTTP response code matches the expectation, and stores
+ * in the state what came from the backend.
+ *
+ * @param cls closure
+ * @param hr HTTP response
+ */
+static void
+track_transaction_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct TrackTransactionState *tts = cls;
+
+ tts->tth = NULL;
+ if (tts->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (tts->is));
+ TALER_TESTING_interpreter_fail (tts->is);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "/track/transaction, response code: %u\n",
+ hr->http_status);
+ if (MHD_HTTP_OK == hr->http_status)
+ {
+ /* Only storing first element's wtid, as this works around
+ * the disability of the real bank to provide a "bank check"
+ * CMD as the fakebank does. */
+ json_t *wtid_str;
+ json_t *exchange_url;
+
+ if (NULL == (wtid_str
+ = json_object_get (json_array_get (hr->reply,
+ 0),
+ "wtid")))
+ {
+ TALER_TESTING_interpreter_fail (tts->is);
+ return;
+ }
+
+ if (NULL == (exchange_url
+ = json_object_get (json_array_get (hr->reply,
+ 0),
+ "exchange")))
+ {
+ TALER_TESTING_interpreter_fail (tts->is);
+ return;
+ }
+
+ tts->exchange_url = GNUNET_strdup (json_string_value (exchange_url));
+ tts->wtid_str = GNUNET_strdup (json_string_value (wtid_str));
+ }
+ TALER_TESTING_interpreter_next (tts->is);
+}
+
+
+/**
+ * Run the "track transaction" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+track_transaction_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TrackTransactionState *tts = cls;
+ const char *order_id;
+ const struct TALER_TESTING_Command *pay_cmd;
+
+ tts->is = is;
+ if (NULL ==
+ (pay_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ tts->pay_reference)))
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_order_id (pay_cmd,
+ 0,
+ &order_id))
+ TALER_TESTING_FAIL (is);
+
+ tts->tth = TALER_MERCHANT_track_transaction (is->ctx,
+ tts->merchant_url,
+ order_id,
+ &track_transaction_cb,
+ tts);
+ GNUNET_assert (NULL != tts->tth);
+}
+
+
+/**
+ * Free the state of a "track transaction" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+track_transaction_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct TrackTransactionState *tts = cls;
+
+ if (NULL != tts->tth)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/track/transaction (test) operation"
+ " did not complete\n");
+ TALER_MERCHANT_track_transaction_cancel (tts->tth);
+ }
+
+ /* Need to discard 'const' before freeing. */
+ GNUNET_free_non_null ((char *) tts->exchange_url);
+ GNUNET_free_non_null ((char *) tts->wtid_str);
+ GNUNET_free (tts);
+}
+
+
+/**
+ * Offer internal data of a "track transaction" CMD, for
+ * other CMDs to use.
+ *
+ * @param cls closure.
+ * @param ret[out] return value.
+ * @param trait name of the trait.
+ * @param index index of the trait.
+ * @return #GNUNET_OK if it is successful.
+ */
+static int
+track_transaction_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct TrackTransactionState *tts = cls;
+ struct TALER_WireTransferIdentifierRawP *wtid_ptr;
+
+ if (MHD_HTTP_OK != tts->http_status)
+ return GNUNET_SYSERR;
+ if ( (NULL != tts->wtid_str) &&
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (tts->wtid_str,
+ strlen (tts->wtid_str),
+ &tts->wtid,
+ sizeof (struct
+ TALER_WireTransferIdentifierRawP))) )
+ wtid_ptr = NULL;
+ else
+ wtid_ptr = &tts->wtid;
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_wtid (0, wtid_ptr),
+ TALER_TESTING_make_trait_url (0, tts->exchange_url),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
+}
+
+
+/**
+ * Define a "track transaction" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * /track/transaction request.
+ * @param http_status expected HTTP response code.
+ * @param pay_reference used to retrieve the order id to track.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transaction (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *pay_reference)
+{
+ struct TrackTransactionState *tts;
+
+ tts = GNUNET_new (struct TrackTransactionState);
+ tts->merchant_url = merchant_url;
+ tts->http_status = http_status;
+ tts->pay_reference = pay_reference;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tts,
+ .label = label,
+ .run = &track_transaction_run,
+ .cleanup = &track_transaction_cleanup,
+ .traits = &track_transaction_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_track_transaction.c */
diff --git a/src/testing/testing_api_cmd_track_transfer.c b/src/testing/testing_api_cmd_track_transfer.c
new file mode 100644
index 00000000..b0dfc478
--- /dev/null
+++ b/src/testing/testing_api_cmd_track_transfer.c
@@ -0,0 +1,322 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_track_transfer.c
+ * @brief command to test /track/transfer.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "track transfer" CMD.
+ */
+struct TrackTransferState
+{
+
+ /**
+ * Handle for a "track transfer" request.
+ */
+ struct TALER_MERCHANT_TrackTransferHandle *tth;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Reference for a "check bank" CMD. It offers the
+ * WTID to track.
+ */
+ const char *check_bank_reference;
+
+};
+
+
+/**
+ * Callback for a /track/transfer operation, only checks if
+ * response code is the expected one.
+ *
+ * @param cls closure for this function
+ * @param http_status HTTP response code returned by the server
+ * @param ec taler-specific error code
+ * @param sign_key exchange key used to sign @a json, or NULL
+ * @param json original json reply (may include signatures,
+ * those have then been validated already)
+ * @param h_wire hash of the wire transfer address the transfer
+ * went to, or NULL on error
+ * @param total_amount total amount of the wire transfer, or NULL
+ * if the exchange could not provide any @a wtid (set only
+ * if @a http_status is #MHD_HTTP_OK)
+ * @param details_length length of the @a details array
+ * @param details array with details about the combined
+ * transactions
+ */
+static void
+track_transfer_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_ExchangePublicKeyP *sign_key,
+ const struct GNUNET_HashCode *h_wire,
+ const struct TALER_Amount *total_amount,
+ unsigned int details_length,
+ const struct TALER_MERCHANT_TrackTransferDetails *details)
+{
+ /* FIXME, deeper checks should be implemented here. */
+ struct TrackTransferState *tts = cls;
+
+ tts->tth = NULL;
+ if (tts->http_status != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ hr->http_status,
+ (int) hr->ec,
+ TALER_TESTING_interpreter_get_current_label (tts->is));
+ TALER_TESTING_interpreter_fail (tts->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ /**
+ * Check that all the deposits sum up to the total
+ * transferred amount. */
+ case MHD_HTTP_OK:
+ {
+ json_t *deposits;
+ const char *amount_str;
+ struct TALER_Amount total;
+ struct TALER_Amount wire_fee;
+ struct TALER_Amount amount_iter;
+ struct TALER_Amount deposit_fee_iter;
+ struct TALER_Amount sum;
+ size_t index;
+ json_t *value;
+
+ amount_str = json_string_value (json_object_get (hr->reply,
+ "total"));
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount_str,
+ &total))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s'\n",
+ amount_str);
+ TALER_TESTING_FAIL (tts->is);
+ return;
+ }
+ amount_str = json_string_value (json_object_get (hr->reply,
+ "wire_fee"));
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount_str,
+ &wire_fee))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s'\n",
+ amount_str);
+ TALER_TESTING_FAIL (tts->is);
+ return;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (total.currency,
+ &sum));
+ deposits = json_object_get (hr->reply,
+ "deposits_sums");
+ json_array_foreach (deposits, index, value)
+ {
+ amount_str = json_string_value (json_object_get (value,
+ "deposit_value"));
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount_str,
+ &amount_iter))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s'\n",
+ amount_str);
+ TALER_TESTING_FAIL (tts->is);
+ return;
+ }
+ amount_str = json_string_value (json_object_get (value,
+ "deposit_fee"));
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount_str,
+ &deposit_fee_iter))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s'\n",
+ amount_str);
+ TALER_TESTING_FAIL (tts->is);
+ return;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&sum,
+ &sum,
+ &amount_iter));
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&sum,
+ &sum,
+ &deposit_fee_iter));
+ }
+
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&sum,
+ &sum,
+ &wire_fee));
+ if (0 != TALER_amount_cmp (&sum,
+ &total))
+ {
+ GNUNET_break (0);
+ TALER_LOG_ERROR (
+ "Inconsistent amount transferred: Sum %s, claimed %s\n",
+ TALER_amount_to_string (&sum),
+ TALER_amount_to_string (&total));
+ TALER_TESTING_interpreter_fail (tts->is);
+ }
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ TALER_TESTING_interpreter_next (tts->is);
+}
+
+
+/**
+ * Run the "track transfer" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+track_transfer_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TrackTransferState *tts = cls;
+ const struct TALER_WireTransferIdentifierRawP *wtid;
+ const struct TALER_TESTING_Command *check_bank_cmd;
+ const char *exchange_url;
+
+ tts->is = is;
+ check_bank_cmd
+ = TALER_TESTING_interpreter_lookup_command (is,
+ tts->check_bank_reference);
+ if (NULL == check_bank_cmd)
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_wtid (check_bank_cmd,
+ 0,
+ &wtid))
+ TALER_TESTING_FAIL (is);
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_url (check_bank_cmd,
+ TALER_TESTING_UT_EXCHANGE_BASE_URL,
+ &exchange_url))
+ TALER_TESTING_FAIL (is);
+ tts->tth = TALER_MERCHANT_track_transfer (is->ctx,
+ tts->merchant_url,
+ "x-taler-bank",
+ wtid,
+ exchange_url,
+ &track_transfer_cb,
+ tts);
+ GNUNET_assert (NULL != tts->tth);
+}
+
+
+/**
+ * Free the state of a "track transfer" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+track_transfer_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct TrackTransferState *tts = cls;
+
+ if (NULL != tts->tth)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/track/transfer (test) operation"
+ " did not complete\n");
+ TALER_MERCHANT_track_transfer_cancel (tts->tth);
+ }
+ GNUNET_free (tts);
+}
+
+
+/**
+ * Define a "track transfer" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * /track/transfer request.
+ * @param http_status expected HTTP response code.
+ * @param check_bank_reference reference to a "check bank" CMD
+ * that will provide the WTID and exchange URL to issue
+ * the track against.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transfer (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *check_bank_reference)
+{
+ struct TrackTransferState *tts;
+
+ tts = GNUNET_new (struct TrackTransferState);
+ tts->merchant_url = merchant_url;
+ tts->http_status = http_status;
+ tts->check_bank_reference = check_bank_reference;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tts,
+ .label = label,
+ .run = &track_transfer_run,
+ .cleanup = &track_transfer_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_track_transfer.c */
diff --git a/src/testing/testing_api_helpers.c b/src/testing/testing_api_helpers.c
new file mode 100644
index 00000000..326bfcb8
--- /dev/null
+++ b/src/testing/testing_api_helpers.c
@@ -0,0 +1,195 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_helpers.c
+ * @brief helper functions for test library.
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * Start the merchant backend process. Assume the port
+ * is available and the database is clean. Use the "prepare
+ * merchant" function to do such tasks.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the process, or NULL if the process could not
+ * be started.
+ */
+struct GNUNET_OS_Process *
+TALER_TESTING_run_merchant (const char *config_filename,
+ const char *merchant_url)
+{
+ struct GNUNET_OS_Process *merchant_proc;
+ unsigned int iter;
+ char *wget_cmd;
+
+ merchant_proc
+ = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-merchant-httpd",
+ "taler-merchant-httpd",
+ "--log=INFO",
+ "-c", config_filename,
+ NULL);
+ if (NULL == merchant_proc)
+ MERCHANT_FAIL ();
+
+ GNUNET_asprintf (&wget_cmd,
+ "wget -q -t 1 -T 1"
+ " --header='Authorization: ApiKey sandbox'"
+ " %s"
+ " -o /dev/null -O /dev/null",
+ merchant_url);
+
+ /* give child time to start and bind against the socket */
+ fprintf (stderr,
+ "Waiting for `taler-merchant-httpd' to be ready\n");
+ iter = 0;
+ do
+ {
+ if (10 == iter)
+ {
+ fprintf (stderr,
+ "Failed to launch"
+ " `taler-merchant-httpd' (or `wget')\n");
+ GNUNET_OS_process_kill (merchant_proc,
+ SIGTERM);
+ GNUNET_OS_process_wait (merchant_proc);
+ GNUNET_OS_process_destroy (merchant_proc);
+ MERCHANT_FAIL ();
+ }
+ fprintf (stderr, ".\n");
+ sleep (1);
+ iter++;
+ }
+ while (0 != system (wget_cmd));
+ GNUNET_free (wget_cmd);
+ fprintf (stderr, "\n");
+
+ return merchant_proc;
+}
+
+
+/**
+ * Prepare the merchant execution. Create tables and check if
+ * the port is available.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the base url, or NULL upon errors. Must be freed
+ * by the caller.
+ */
+char *
+TALER_TESTING_prepare_merchant (const char *config_filename)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ unsigned long long port;
+ struct GNUNET_OS_Process *dbinit_proc;
+ enum GNUNET_OS_ProcessStatusType type;
+ unsigned long code;
+ char *base_url;
+
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (cfg,
+ config_filename))
+ MERCHANT_FAIL ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "merchant",
+ "PORT",
+ &port))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "merchant",
+ "PORT");
+ GNUNET_CONFIGURATION_destroy (cfg);
+ MERCHANT_FAIL ();
+ }
+
+ GNUNET_CONFIGURATION_destroy (cfg);
+
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
+ (uint16_t) port))
+ {
+ fprintf (stderr,
+ "Required port %llu not available, skipping.\n",
+ port);
+ MERCHANT_FAIL ();
+ }
+
+ /* DB preparation */
+ if (NULL == (dbinit_proc = GNUNET_OS_start_process
+ (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-merchant-dbinit",
+ "taler-merchant-dbinit",
+ "-c", config_filename,
+ "-r",
+ NULL)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to run taler-merchant-dbinit."
+ " Check your PATH.\n");
+ MERCHANT_FAIL ();
+ }
+
+ if (GNUNET_SYSERR ==
+ GNUNET_OS_process_wait_status (dbinit_proc,
+ &type,
+ &code))
+ {
+ GNUNET_OS_process_destroy (dbinit_proc);
+ MERCHANT_FAIL ();
+ }
+ if ( (type == GNUNET_OS_PROCESS_EXITED) &&
+ (0 != code) )
+ {
+ fprintf (stderr,
+ "Failed to setup database\n");
+ MERCHANT_FAIL ();
+ }
+ if ( (type != GNUNET_OS_PROCESS_EXITED) ||
+ (0 != code) )
+ {
+ fprintf (stderr,
+ "Unexpected error running"
+ " `taler-merchant-dbinit'!\n");
+ MERCHANT_FAIL ();
+ }
+ GNUNET_OS_process_destroy (dbinit_proc);
+
+
+ GNUNET_asprintf (&base_url,
+ "http://localhost:%llu/",
+ port);
+ return base_url;
+}
diff --git a/src/testing/testing_api_trait_hash.c b/src/testing/testing_api_trait_hash.c
new file mode 100644
index 00000000..08e50274
--- /dev/null
+++ b/src/testing/testing_api_trait_hash.c
@@ -0,0 +1,126 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_trait_hash.c
+ * @brief offer any trait that is passed over as a hash code.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+
+/**
+ * TODO: have _one_ pair of getter/setter for hash-coded
+ * traits and define the getters/setters for tip id and hashed
+ * contract terms as _macros_.
+ */
+
+#define TALER_TESTING_TRAIT_TIP_ID "tip-id"
+#define TALER_TESTING_TRAIT_H_CONTRACT_TERMS "h-contract-terms"
+
+/**
+ * Obtain tip id from a @a cmd.
+ *
+ * @param cmd command to extract the trait from.
+ * @param index which tip id to pick if @a
+ * cmd has multiple on offer
+ * @param tip_id[out] set to the wanted data.
+ *
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_tip_id
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ struct GNUNET_HashCode **tip_id)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) tip_id,
+ TALER_TESTING_TRAIT_TIP_ID,
+ index);
+}
+
+
+/**
+ * Offer tip id.
+ *
+ * @param index which tip id to offer if there are
+ * multiple on offer.
+ * @param tip_id set to the offered tip id.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_tip_id
+ (unsigned int index,
+ const struct GNUNET_HashCode *tip_id)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = TALER_TESTING_TRAIT_TIP_ID,
+ .ptr = (const void *) tip_id
+ };
+ return ret;
+}
+
+
+/**
+ * Obtain contract terms hash from a @a cmd.
+ *
+ * @param cmd command to extract the trait from.
+ * @param index index number of the trait to fetch.
+ * @param h_contract_terms[out] set to the wanted data.
+ *
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_h_contract_terms
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct GNUNET_HashCode **h_contract_terms)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) h_contract_terms,
+ TALER_TESTING_TRAIT_H_CONTRACT_TERMS,
+ index);
+}
+
+
+/**
+ * Offer contract terms hash code.
+ *
+ * @param index which hashed contract terms to
+ * offer if there are multiple on offer
+ * @param h_contract_terms set to the offered hashed
+ * contract terms.
+ *
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_h_contract_terms
+ (unsigned int index,
+ const struct GNUNET_HashCode *h_contract_terms)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = TALER_TESTING_TRAIT_H_CONTRACT_TERMS,
+ .ptr = (const void *) h_contract_terms
+ };
+ return ret;
+}
diff --git a/src/testing/testing_api_trait_merchant_sig.c b/src/testing/testing_api_trait_merchant_sig.c
new file mode 100644
index 00000000..5a2f5ebb
--- /dev/null
+++ b/src/testing/testing_api_trait_merchant_sig.c
@@ -0,0 +1,77 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_trait_merchant_sig.c
+ * @brief offer merchant signature over contract
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+
+#define TALER_TESTING_TRAIT_MERCHANT_SIG "reserve-private-key"
+
+/**
+ * Obtain a merchant signature over a contract from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which signature to pick if @a cmd has multiple
+ * on offer
+ * @param merchant_sig[out] set to the wanted signature.
+ *
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_merchant_sig
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ struct TALER_MerchantSignatureP **merchant_sig)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) merchant_sig,
+ TALER_TESTING_TRAIT_MERCHANT_SIG,
+ index);
+}
+
+
+/**
+ * Offer a merchant signature over a contract.
+ *
+ * @param index which signature to offer if there are multiple
+ * on offer
+ * @param merchant_sig set to the offered signature.
+ *
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_merchant_sig
+ (unsigned int index,
+ const struct TALER_MerchantSignatureP *merchant_sig)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = TALER_TESTING_TRAIT_MERCHANT_SIG,
+ .ptr = (const void *) merchant_sig
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_merchant_sig.c */
diff --git a/src/testing/testing_api_trait_planchet.c b/src/testing/testing_api_trait_planchet.c
new file mode 100644
index 00000000..165ce818
--- /dev/null
+++ b/src/testing/testing_api_trait_planchet.c
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_trait_planchet.c
+ * @brief offer planchet secrets as trait.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+
+#define TALER_TESTING_TRAIT_PLANCHET_SECRETS "planchet-secrets"
+
+/**
+ * Obtain planchet secrets from a @a cmd.
+ *
+ * @param cmd command to extract trait from.
+ * @param index index of the trait.
+ * @param planchet_secrets[out] set to the wanted secrets.
+ *
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_planchet_secrets
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ struct TALER_PlanchetSecretsP **planchet_secrets)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) planchet_secrets,
+ TALER_TESTING_TRAIT_PLANCHET_SECRETS,
+ index);
+}
+
+
+/**
+ * Offer planchet secrets.
+ *
+ * @param index of the trait.
+ * @param planchet_secrets set to the offered secrets.
+ *
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_planchet_secrets
+ (unsigned int index,
+ const struct TALER_PlanchetSecretsP *planchet_secrets)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = TALER_TESTING_TRAIT_PLANCHET_SECRETS,
+ .ptr = (const void *) planchet_secrets
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_planchet.c */
diff --git a/src/testing/testing_api_trait_refund_entry.c b/src/testing/testing_api_trait_refund_entry.c
new file mode 100644
index 00000000..6a27cfbd
--- /dev/null
+++ b/src/testing/testing_api_trait_refund_entry.c
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_trait_refund_entry.c
+ * @brief command to offer refund entry trait.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+
+#define TALER_TESTING_TRAIT_REFUND_ENTRY "refund-entry"
+
+/**
+ * Obtain refund entry from a @a cmd.
+ *
+ * @param cmd command to extract the trait from.
+ * @param index the trait index.
+ * @param refund_entry[out] set to the wanted data.
+ *
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_refund_entry
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct TALER_MERCHANT_RefundEntry **refund_entry)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) refund_entry,
+ TALER_TESTING_TRAIT_REFUND_ENTRY,
+ index);
+}
+
+
+/**
+ * Offer refund entry.
+ *
+ * @param index index number of the trait to offer.
+ * @param refund_entry set to the offered refund entry.
+ *
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_refund_entry
+ (unsigned int index,
+ const struct TALER_MERCHANT_RefundEntry *refund_entry)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = TALER_TESTING_TRAIT_REFUND_ENTRY,
+ .ptr = (const void *) refund_entry
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_refund_entry.c */
diff --git a/src/testing/testing_api_trait_string.c b/src/testing/testing_api_trait_string.c
new file mode 100644
index 00000000..30db2f6a
--- /dev/null
+++ b/src/testing/testing_api_trait_string.c
@@ -0,0 +1,136 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2018 Taler Systems SA
+
+ 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.
+
+ 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 TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_trait_string.c
+ * @brief offer traits that come as strings.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+
+#define TALER_TESTING_TRAIT_PROPOSAL_REFERENCE "proposal-reference"
+#define TALER_TESTING_TRAIT_COIN_REFERENCE "coin-reference"
+
+/**
+ * Obtain a reference to a proposal command. Any command that
+ * works with proposals, might need to offer their reference to
+ * it. Notably, the "pay" command, offers its proposal reference
+ * to the "pay abort" command as the latter needs to reconstruct
+ * the same data needed by the former in order to use the "pay
+ * abort" API.
+ *
+ * @param cmd command to extract the trait from.
+ * @param index which reference to pick if @a cmd has multiple
+ * on offer.
+ * @param proposal_reference[out] set to the wanted reference.
+ *
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_proposal_reference
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const char **proposal_reference)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) proposal_reference,
+ TALER_TESTING_TRAIT_PROPOSAL_REFERENCE,
+ index);
+}
+
+
+/**
+ * Offer a proposal reference.
+ *
+ * @param index which reference to offer if there are
+ * multiple on offer.
+ * @param proposal_reference pointer to the reference to offer.
+ *
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_proposal_reference
+ (unsigned int index,
+ const char *proposal_reference)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = TALER_TESTING_TRAIT_PROPOSAL_REFERENCE,
+ .ptr = (const void *) proposal_reference
+ };
+ return ret;
+}
+
+
+/**
+ * Obtain a reference to any command that can provide coins as
+ * traits.
+ *
+ * @param cmd command to extract trait from
+ * @param index which reference to pick if @a cmd has multiple
+ * on offer
+ * @param coin_reference[out] set to the wanted reference.
+ * NOTE: a _single_ reference can contain
+ * _multiple_ instances, using semi-colon as separator.
+ * For example, a _single_ reference can be this:
+ * "coin-ref-1", or even this: "coin-ref-1;coin-ref-2".
+ * The "pay" command contains functions that can parse
+ * such format.
+ *
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_coin_reference
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const char **coin_reference)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) coin_reference,
+ TALER_TESTING_TRAIT_COIN_REFERENCE,
+ index);
+}
+
+
+/**
+ * Offer a coin reference.
+ *
+ * @param index which reference to offer if there are
+ * multiple on offer.
+ * @param coin_reference set to the offered reference.
+ *
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_coin_reference
+ (unsigned int index,
+ const char *coin_reference)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = TALER_TESTING_TRAIT_COIN_REFERENCE,
+ .ptr = (const void *) coin_reference
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_string.c */
diff --git a/src/testing/tor_merchant.priv b/src/testing/tor_merchant.priv
new file mode 100644
index 00000000..facd4dfa
--- /dev/null
+++ b/src/testing/tor_merchant.priv
@@ -0,0 +1 @@
+=W#K-zN;qro<{nN \ No newline at end of file