aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcello Stanisci <stanisci.m@gmail.com>2018-02-12 17:34:59 +0100
committerMarcello Stanisci <stanisci.m@gmail.com>2018-02-21 17:04:16 +0100
commit481eda431dca5a450ebcc6949753e8ba85e1f9be (patch)
treec65689f1e2c980cff77e56e0e04bc153e1242fe5
parent7820b4b0021ba63027d140765c4167feeee40246 (diff)
Implementing new style test instructions.
-rw-r--r--.gitignore1
-rw-r--r--src/include/taler_merchant_testing_lib.h507
-rw-r--r--src/lib/Makefile.am51
-rw-r--r--src/lib/merchant_api_refund.c9
-rw-r--r--src/lib/test_merchant_api.c4
-rw-r--r--src/lib/test_merchant_api.conf10
-rw-r--r--src/lib/test_merchant_api_new.c695
-rw-r--r--src/lib/testing_api_cmd_history.c302
-rw-r--r--src/lib/testing_api_cmd_pay.c1538
-rw-r--r--src/lib/testing_api_cmd_proposal.c560
-rw-r--r--src/lib/testing_api_cmd_refund.c457
-rw-r--r--src/lib/testing_api_cmd_tip.c777
-rw-r--r--src/lib/testing_api_cmd_track.c362
-rw-r--r--src/lib/testing_api_helpers.c185
-rw-r--r--src/lib/testing_api_trait_hash.c117
-rw-r--r--src/lib/testing_api_trait_merchant_sig.c73
-rw-r--r--src/lib/testing_api_trait_planchet.c73
-rw-r--r--src/lib/testing_api_trait_refund_entry.c77
-rw-r--r--src/lib/testing_api_trait_string.c131
19 files changed, 5919 insertions, 10 deletions
diff --git a/.gitignore b/.gitignore
index f2f8d1f4..cd4a016a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@ GTAGS
src/backend/taler-merchant-httpd
src/merchant-tools/taler-merchant-dbinit
src/lib/test_merchant_api
+src/lib/test_merchant_api_new
src/lib/test_merchant_api_home/.local/share/taler/exchange/live-keys/
src/lib/test_merchant_api_home/.local/share/taler/wirefees/
taler_merchant_config.h
diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h
new file mode 100644
index 00000000..53f04693
--- /dev/null
+++ b/src/include/taler_merchant_testing_lib.h
@@ -0,0 +1,507 @@
+/*
+ This file is part of TALER
+ (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 include/taler_testing_lib.h
+ * @brief API for writing an interpreter to test Taler components
+ * @author Christian Grothoff <christian@grothoff.org>
+ * @author Marcello Stanisci
+ */
+#ifndef TALER_MERCHANT_TESTING_LIB_H
+#define TALER_MERCHANT_TESTING_LIB_H
+
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+
+/* ********************* Helper functions ********************* */
+
+
+#define MERCHANT_FAIL() \
+ do {GNUNET_break (0); return NULL; } while (0)
+
+
+#define CMD_NOT_FOUND "Command not found"
+#define TRAIT_NOT_FOUND "Trait not found"
+
+#define TALER_TESTING_FAIL(is) \
+ do \
+ {\
+ GNUNET_break (0); \
+ TALER_TESTING_interpreter_fail (is); \
+ return; \
+ } while (0)
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/* ******************* Generic interpreter logic ************ */
+
+/* ************** Specific interpreter commands ************ */
+
+/**
+ * Make the /proposal command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base url.
+ * @param ctx context
+ * @param http_status HTTP status code.
+ * @param order the order
+ * @param instance the merchant instance
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *order,
+ const char *instance);
+
+/**
+ * Make a "proposal lookup" command.
+ *
+ * @param label command label
+ * @param http_status expected HTTP response code
+ * @param proposal_reference reference to a proposal command
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal_lookup
+ (const char *label,
+ struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference);
+
+/**
+ * Make a "check payment" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param ctx CURL context.
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *proposal_reference,
+ unsigned int expect_paid);
+
+/**
+ * Make a "pay" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param ctx CURL context.
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ 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);
+
+/**
+ * Make a "pay again" test command.
+ *
+ * @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 ctx main CURL context
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status);
+/**
+ * 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 ctx main CURL context
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status);
+
+/**
+ * FIXME.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_abort_refund
+ (const char *label,
+ struct TALER_EXCHANGE_Handle *exchange,
+ const char *abort_reference,
+ unsigned int num_coins,
+ const char *refund_amount,
+ const char *refund_fee,
+ unsigned int http_status);
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_lookup
+ (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ const char *increase_reference,
+ const char *pay_reference,
+ const char *order_id);
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_increase
+ (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ const char *reason,
+ const char *order_id,
+ const char *refund_amount,
+ const char *refund_fee);
+
+
+/**
+ * Make a "history" command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param ctx main CURL context
+ * @param http_status expected HTTP response code
+ * @param time FIXME
+ * @param nresult how many results are expected
+ * @param start FIXME.
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ struct GNUNET_TIME_Absolute time,
+ unsigned int nresult,
+ unsigned int start,
+ unsigned int nrows);
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transaction
+ (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *transfer_reference,
+ const char *pay_reference,
+ const char *wire_fee);
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transfer
+ (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *check_bank_reference,
+ const char *pay_reference);
+
+/* ****** Specific traits supported by this component ******* */
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+
+/**
+ * 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 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);
+
+/**
+ * Offer a proposal reference.
+ *
+ * @param index which reference to offer if there are
+ * multiple on offer
+ * @param proposal_reference set to the offered reference.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_proposal_reference
+ (unsigned int index,
+ const char *proposal_reference);
+
+/**
+ * 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);
+
+/**
+ * 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_ token,
+ * 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);
+
+
+/**
+ * Obtain planchet secrets 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 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);
+
+
+/**
+ * Offer planchet secrets.
+ *
+ * @param index which secrets to offer if there are multiple
+ * on offer
+ * @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);
+
+/**
+ * Offer tip id.
+ *
+ * @param index which tip id to offer if there are
+ * multiple on offer
+ * @param planchet_secrets set to the offered secrets.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_tip_id
+ (unsigned int index,
+ const struct GNUNET_HashCode *tip_id);
+
+
+/**
+ * Obtain tip id 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 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,
+ const struct GNUNET_HashCode **tip_id);
+
+/**
+ * Offer contract terms hash code.
+ *
+ * @param index which hash code to offer if there are
+ * multiple on offer
+ * @param h_contract_terms set to the offered hash code.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_h_contract_terms
+ (unsigned int index,
+ const struct GNUNET_HashCode *h_contract_terms);
+
+/**
+ * Obtain contract terms hash from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which hash code to pick if @a cmd has multiple
+ * on offer
+ * @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 const struct GNUNET_HashCode **h_contract_terms);
+
+
+/**
+ * Offer refund entry.
+ *
+ * @param index which tip id to offer if there are
+ * multiple on 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);
+
+
+/**
+ * Obtain refund entry 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 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);
+
+#endif
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index d953081f..93f154b7 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -7,12 +7,17 @@ if USE_COVERAGE
endif
lib_LTLIBRARIES = \
- libtalermerchant.la
+ libtalermerchant.la \
+ libtalermerchanttesting.la
libtalermerchant_la_LDFLAGS = \
-version-info 2:0:0 \
-no-undefined
+libtalermerchanttesting_la_LDFLAGS = \
+ -version-info 2:0:0 \
+ -no-undefined
+
libtalermerchant_la_SOURCES = \
merchant_api_proposal.c \
merchant_api_pay.c \
@@ -35,6 +40,31 @@ libtalermerchant_la_LIBADD = \
-ljansson \
$(XLIB)
+libtalermerchanttesting_la_SOURCES = \
+ testing_api_cmd_proposal.c \
+ testing_api_cmd_pay.c \
+ testing_api_cmd_refund.c \
+ testing_api_cmd_tip.c \
+ testing_api_cmd_track.c \
+ testing_api_cmd_history.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 = \
+ -ltalerexchange \
+ -ltalerjson \
+ -ltalerutil \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ -ltalertesting \
+ $(XLIB)
+
if HAVE_LIBCURL
libtalermerchant_la_LIBADD += -lcurl
else
@@ -45,12 +75,31 @@ endif
if HAVE_TALERFAKEBANK
check_PROGRAMS = \
+ test_merchant_api_new \
test_merchant_api
endif
TESTS = \
$(check_PROGRAMS)
+test_merchant_api_new_SOURCES = \
+ test_merchant_api_new.c
+test_merchant_api_new_LDADD = \
+ $(top_srcdir)/src/backenddb/libtalermerchantdb.la \
+ libtalermerchant.la \
+ $(LIBGCRYPT_LIBS) \
+ -ltalertesting \
+ -ltalermerchanttesting \
+ -ltalerfakebank \
+ -ltalerbank \
+ -ltalerexchange \
+ -ltalerjson \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson
+
test_merchant_api_SOURCES = \
test_merchant_api.c
test_merchant_api_LDADD = \
diff --git a/src/lib/merchant_api_refund.c b/src/lib/merchant_api_refund.c
index 01fa6bd3..2f02735b 100644
--- a/src/lib/merchant_api_refund.c
+++ b/src/lib/merchant_api_refund.c
@@ -316,17 +316,20 @@ TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx,
{
struct TALER_MERCHANT_RefundLookupOperation *rlo;
CURL *eh;
+ char *base;
rlo = GNUNET_new (struct TALER_MERCHANT_RefundLookupOperation);
rlo->ctx = ctx;
rlo->cb = cb;
rlo->cb_cls = cb_cls;
-
+
+ base = TALER_url_join (backend_url, "/public/refund", NULL);
GNUNET_asprintf (&rlo->url,
- "%s/public/refund?instance=%s&order_id=%s",
- backend_url,
+ "%s?instance=%s&order_id=%s",
+ base,
instance,
order_id);
+ GNUNET_free (base);
eh = curl_easy_init ();
if (CURLE_OK != curl_easy_setopt (eh,
CURLOPT_URL,
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index c8ae9f82..c968168c 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -40,7 +40,7 @@
/**
* URL under which the exchange is reachable during the testcase.
*/
-#define EXCHANGE_URL "http://localhost:8084/"
+#define EXCHANGE_URL "http://localhost:8081/"
/**
* Account number of the exchange at the bank.
@@ -1299,6 +1299,7 @@ history_cb (void *cls,
fail (is);
return;
}
+ /* entry_timestamp should always become last_timestamp */
entry_timestamp = GNUNET_TIME_absolute_max (last_timestamp, entry_timestamp);
if (last_timestamp.abs_value_us != entry_timestamp.abs_value_us)
{
@@ -3822,6 +3823,7 @@ interpreter_run (void *cls)
const struct Command *proposal_ref;
const char *order_id;
+ /* get proposal reference, and order_id from it */
GNUNET_assert(NULL != (ref = find_command
(is,
cmd->details.track_transaction.pay_ref)));
diff --git a/src/lib/test_merchant_api.conf b/src/lib/test_merchant_api.conf
index 924aaa52..ef819e48 100644
--- a/src/lib/test_merchant_api.conf
+++ b/src/lib/test_merchant_api.conf
@@ -72,7 +72,7 @@ CLOSING-FEE-2026 = EUR:0.01
[merchant-exchange-test]
-URL = http://localhost:8084/
+URL = http://localhost:8081/
MASTER_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
[merchant-instance-default]
@@ -88,13 +88,13 @@ NAME = The Tor Project
[merchant-instance-tip]
KEYFILE = reserve_tip.priv
-TIP_EXCHANGE = http://localhost:8084/
+TIP_EXCHANGE = http://localhost:8081/
TIP_RESERVE_PRIV_FILENAME = reserve_key.priv
NAME = Test Tipping Merchant
[merchant-instance-dtip]
KEYFILE = reserve_dtip.priv
-TIP_EXCHANGE = http://localhost:8084/
+TIP_EXCHANGE = http://localhost:8081/
TIP_RESERVE_PRIV_FILENAME = reserve_dkey.priv
NAME = Test Tipping Merchant 2
@@ -144,13 +144,13 @@ ADDRESS = "Garching"
DB = postgres
# HTTP port the exchange listens to
-PORT = 8084
+PORT = 8081
# Our public key
MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
# Base URL of the exchange.
-BASE_URL = http://localhost:8084/
+BASE_URL = "http://localhost:8081/"
[exchangedb-postgres]
DB_CONN_STR = "postgres:///talercheck"
diff --git a/src/lib/test_merchant_api_new.c b/src/lib/test_merchant_api_new.c
new file mode 100644
index 00000000..8615d6a7
--- /dev/null
+++ b/src/lib/test_merchant_api_new.c
@@ -0,0 +1,695 @@
+/*
+ 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_new.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_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/"
+
+/**
+ * URL of the fakebank. Obtained from CONFIG_FILE's
+ * "exchange-wire-test:BANK_URI" option.
+ */
+static char *fakebank_url;
+
+/**
+ * Merchant base URL.
+ */
+static char *merchant_url;
+
+/**
+ * Merchant process.
+ */
+static struct GNUNET_OS_Process *merchantd;
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NO 2
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NO 62
+
+/**
+ * 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.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+ TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE)
+
+/**
+ * Execute the taler-exchange-aggregator command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+ TALER_TESTING_cmd_exec_aggregator (label, 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
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+ TALER_TESTING_cmd_fakebank_transfer (label, amount, \
+ fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \
+ USER_LOGIN_NAME, USER_LOGIN_PASS, EXCHANGE_URL)
+
+/**
+ * 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"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE_SUBJECT(label,amount,subject) \
+ TALER_TESTING_cmd_fakebank_transfer_with_subject \
+ (label, amount, fakebank_url, USER_ACCOUNT_NO, \
+ EXCHANGE_ACCOUNT_NO, USER_LOGIN_NAME, USER_LOGIN_PASS, \
+ subject)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Merchant serves at `%s'\n",
+ merchant_url);
+
+ struct TALER_TESTING_Command commands[] = {
+
+ /**
+ * 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_transfer
+ ("check_bank_transfer-2",
+ "http://localhost:8081/",
+ "EUR:10.02", USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO),
+
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ is->exchange,
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ is->exchange,
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ /**
+ * Check the reserve is depleted.
+ */
+ TALER_TESTING_cmd_status ("withdraw-status-1",
+ is->exchange,
+ "create-reserve-1",
+ "EUR:0",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_proposal
+ ("create-proposal-1",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\
+ {\"currency\":\"EUR\",\
+ \"value\":0,\
+ \"fraction\":50000000},\
+ \"order_id\":\"1\",\
+ \"refund_deadline\":\"\\/Date(0)\\/\",\
+ \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
+ \"amount\":\
+ {\"currency\":\"EUR\",\
+ \"value\":5,\
+ \"fraction\":0},\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }",
+ NULL),
+
+ TALER_TESTING_cmd_check_payment ("check-payment-1",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "create-proposal-1",
+ GNUNET_NO),
+
+ TALER_TESTING_cmd_pay ("deposit-simple",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "create-proposal-1",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+
+ TALER_TESTING_cmd_check_payment ("check-payment-2",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "create-proposal-1",
+ GNUNET_YES),
+
+ TALER_TESTING_cmd_pay_abort ("pay-abort-2",
+ merchant_url,
+ "deposit-simple",
+ is->ctx,
+ MHD_HTTP_FORBIDDEN),
+
+ TALER_TESTING_cmd_pay ("replay-simple",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "create-proposal-1",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+
+ TALER_TESTING_cmd_proposal
+ ("create-proposal-2",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\
+ {\"currency\":\"EUR\",\
+ \"value\":0,\
+ \"fraction\":50000000},\
+ \"order_id\":\"2\",\
+ \"refund_deadline\":\"\\/Date(0)\\/\",\
+ \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
+ \"amount\":\
+ {\"currency\":\"EUR\",\
+ \"value\":5,\
+ \"fraction\":0},\
+ \"summary\": \"useful product\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:5}\"} ] }",
+ NULL),
+
+ TALER_TESTING_cmd_pay ("deposit-double-2",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_FORBIDDEN,
+ "create-proposal-2",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ "EUR:0.01"),
+
+ TALER_TESTING_cmd_history ("history-0",
+ merchant_url,
+ is->ctx,
+ 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_fakebank_transfer ("create-reserve-2",
+ "EUR:1",
+ fakebank_url,
+ 63, 2,
+ "user63",
+ "pass63",
+ EXCHANGE_URL),
+
+ TALER_TESTING_cmd_fakebank_transfer_with_ref
+ ("create-reserve-2b",
+ "EUR:4.01",
+ fakebank_url,
+ 63, 2,
+ "user63",
+ "pass63",
+ "create-reserve-2",
+ EXCHANGE_URL),
+ CMD_EXEC_WIREWATCH ("wirewatch-2"),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-2",
+ "http://localhost:8081/",
+ "EUR:1", 63, 2),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-2",
+ "http://localhost:8081/",
+ "EUR:4.01", 63, 2),
+
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ is->exchange,
+ "create-reserve-2",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_proposal_lookup ("fetch-proposal-2",
+ is->ctx,
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-proposal-2"),
+
+ 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",
+ "http://localhost:8081/",
+ "EUR:4.98", 2, 62),
+
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-2"),
+
+ TALER_TESTING_cmd_merchant_track_transaction
+ ("track-transaction-1",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c",
+ "deposit-simple",
+ "EUR:0.01"),
+
+ TALER_TESTING_cmd_merchant_track_transfer
+ ("track-transfer-1",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c",
+ "deposit-simple"),
+
+ TALER_TESTING_cmd_merchant_track_transfer
+ ("track-transfer-again",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c",
+ "deposit-simple"),
+
+ TALER_TESTING_cmd_pay ("deposit-simple-2",
+ merchant_url,
+ is->ctx,
+ 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",
+ "http://localhost:8081/",
+ "EUR:4.98",
+ EXCHANGE_ACCOUNT_NO,
+ USER_ACCOUNT_NO),
+
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
+
+ TALER_TESTING_cmd_merchant_track_transfer
+ ("track-transfer-2",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c-2",
+ "deposit-simple-2"),
+
+ TALER_TESTING_cmd_merchant_track_transfer
+ ("track-transfer-2-again",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c-2",
+ "deposit-simple-2"),
+
+ TALER_TESTING_cmd_merchant_track_transaction
+ ("track-transaction-2",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "check_bank_transfer-498c-2",
+ "deposit-simple-2",
+ "EUR:0.01"),
+
+ TALER_TESTING_cmd_history ("history-1",
+ merchant_url,
+ is->ctx,
+ 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_history
+ ("history-2",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ GNUNET_TIME_absolute_add (GNUNET_TIME_UNIT_ZERO_ABS,
+ GNUNET_TIME_UNIT_MICROSECONDS),
+ /* zero results expected, time too ancient. */
+ 0,
+ 10,
+ 10),
+
+ TALER_TESTING_cmd_refund_increase ("refund-increase-1",
+ merchant_url,
+ is->ctx,
+ "refund test",
+ "1",
+ "EUR:0.1",
+ "EUR:0.01"),
+
+ TALER_TESTING_cmd_refund_lookup ("refund-lookup-1",
+ merchant_url,
+ is->ctx,
+ "refund-increase-1",
+ "deposit-simple",
+ "1"),
+ /* test tipping */
+ TALER_TESTING_cmd_fakebank_transfer_with_instance
+ ("create-reserve-10",
+ "EUR:10.02",
+ fakebank_url,
+ USER_ACCOUNT_NO,
+ EXCHANGE_ACCOUNT_NO,
+ USER_LOGIN_NAME,
+ USER_LOGIN_PASS,
+ "tip",
+ EXCHANGE_URL,
+ CONFIG_FILE),
+
+
+ CMD_EXEC_WIREWATCH ("wirewatch-10"),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-10",
+ "http://localhost:8081/",
+ "EUR:10.02", USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO),
+
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10a",
+ is->exchange,
+ "create-reserve-10",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10b",
+ is->exchange,
+ "create-reserve-10",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+
+ TALER_TESTING_cmd_status ("withdraw-status-10",
+ is->exchange,
+ "create-reserve-10",
+ "EUR:0",
+ MHD_HTTP_OK),
+
+
+ TALER_TESTING_cmd_proposal
+ ("create-proposal-10",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\
+ {\"currency\":\"EUR\",\
+ \"value\":0,\
+ \"fraction\":50000000},\
+ \"order_id\":\"10\",\
+ \"refund_deadline\":\"\\/Date(0)\\/\",\
+ \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
+ \"amount\":\
+ {\"currency\":\"EUR\",\
+ \"value\":10,\
+ \"fraction\":0},\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:10}\"} ] }",
+ NULL),
+
+ TALER_TESTING_cmd_pay ("pay-fail-partial-double-10",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_FORBIDDEN,
+ "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",
+ is->ctx,
+ MHD_HTTP_OK),
+
+ CMD_EXEC_AGGREGATOR ("run-aggregator-10"),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-9.97-10",
+ "http://localhost:8081/",
+ "EUR:9.97",
+ EXCHANGE_ACCOUNT_NO,
+ USER_ACCOUNT_NO),
+
+ TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-10"),
+
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-11",
+ "EUR:10.02"),
+
+ CMD_EXEC_WIREWATCH ("wirewatch-11"),
+
+ TALER_TESTING_cmd_check_bank_transfer
+ ("check_bank_transfer-11",
+ "http://localhost:8081/",
+ "EUR:10.02", USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO),
+
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11a",
+ is->exchange,
+ "create-reserve-11",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11b",
+ is->exchange,
+ "create-reserve-11",
+ "EUR:5",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_status ("withdraw-status-11",
+ is->exchange,
+ "create-reserve-11",
+ "EUR:0",
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_proposal
+ ("create-proposal-11",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_OK,
+ "{\"max_fee\":\
+ {\"currency\":\"EUR\",\
+ \"value\":0,\
+ \"fraction\":50000000},\
+ \"order_id\":\"11\",\
+ \"refund_deadline\":\"\\/Date(0)\\/\",\
+ \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
+ \"amount\":\
+ {\"currency\":\"EUR\",\
+ \"value\":10,\
+ \"fraction\":0},\
+ \"summary\": \"merchant-lib testcase\",\
+ \"products\": [ {\"description\":\"ice cream\",\
+ \"value\":\"{EUR:10}\"} ] }",
+ NULL),
+
+ TALER_TESTING_cmd_pay ("pay-fail-partial-double-11",
+ merchant_url,
+ is->ctx,
+ MHD_HTTP_FORBIDDEN,
+ "create-proposal-11",
+ "withdraw-coin-11a;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",
+ is->ctx,
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_pay_abort_refund ("pay-abort-refund-11",
+ is->exchange,
+ /* 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"),
+
+ /**
+ * 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,
+ fakebank_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-new",
+ "DEBUG",
+ NULL);
+
+ if (NULL ==
+ (fakebank_url = TALER_TESTING_prepare_fakebank (
+ CONFIG_FILE)))
+ return 77;
+
+ 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))
+ {
+ 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)))
+ return 1;
+
+ ret = TALER_TESTING_setup_with_exchange (&run,
+ NULL,
+ CONFIG_FILE);
+ GNUNET_OS_process_kill (merchantd, SIGKILL);
+ 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_new.c */
diff --git a/src/lib/testing_api_cmd_history.c b/src/lib/testing_api_cmd_history.c
new file mode 100644
index 00000000..3667d34d
--- /dev/null
+++ b/src/lib/testing_api_cmd_history.c
@@ -0,0 +1,302 @@
+/*
+ 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"
+
+
+struct HistoryState
+{
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * The merchant instance.
+ */
+ const char *instance;
+
+ /**
+ * URL of the merchant backend.
+ */
+ const char *merchant_url;
+
+ /**
+ * The curl context; used to be fed to the merchant lib.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Handle to /history.
+ */
+ struct TALER_MERCHANT_HistoryOperation *ho;
+
+ /**
+ * FIXME
+ */
+ struct GNUNET_TIME_Absolute time;
+
+ /**
+ * FIXME
+ */
+ unsigned int start;
+
+ /**
+ * FIXME
+ */
+ unsigned int nrows;
+
+ /**
+ * FIXME
+ */
+ unsigned int nresult;
+};
+
+/**
+ * Parse given JSON object to absolute time.
+ *
+ * @param root the json object representing data
+ * @param[out] ret where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static int
+parse_abs_time (json_t *root,
+ struct GNUNET_TIME_Absolute *ret)
+{
+ const char *val;
+ unsigned long long int tval;
+
+ val = json_string_value (root);
+ if (NULL == val)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (0 == strcasecmp (val,
+ "/forever/")) ||
+ (0 == strcasecmp (val,
+ "/end of time/")) ||
+ (0 == strcasecmp (val,
+ "/never/")) )
+ {
+ *ret = GNUNET_TIME_UNIT_FOREVER_ABS;
+ return GNUNET_OK;
+ }
+ if (1 != sscanf (val,
+ "/Date(%llu)/",
+ &tval))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* Time is in seconds in JSON, but in microseconds in GNUNET_TIME_Absolute */
+ ret->abs_value_us = tval * 1000LL * 1000LL;
+ if ( (ret->abs_value_us) / 1000LL / 1000LL != tval)
+ {
+ /* Integer overflow */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Callback for a /history request. It's up to this function
+ * how to render the array containing transactions details (FIXME
+ * link to documentation)
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant
+ * backend
+ * @param ec taler-specific error code
+ * @param json actual body containing history
+ */
+static void
+history_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *json)
+{
+
+ struct HistoryState *hs = cls;
+ unsigned int nresult;
+ struct GNUNET_TIME_Absolute last_timestamp;
+ struct GNUNET_TIME_Absolute entry_timestamp;
+
+ hs->ho = NULL;
+ if (hs->http_status != http_status)
+ TALER_TESTING_FAIL (hs->is);
+
+ nresult = json_array_size (json);
+ 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;
+ json_t *timestamp;
+ size_t index;
+ json_array_foreach (json, index, entry)
+ {
+ timestamp = json_object_get (entry, "timestamp");
+ if (GNUNET_OK != parse_abs_time (timestamp, &entry_timestamp))
+ 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);
+}
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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);
+}
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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);
+ }
+ if ( NULL ==
+ ( hs->ho = TALER_MERCHANT_history (hs->ctx,
+ hs->merchant_url,
+ "default",
+ hs->start,
+ hs->nrows,
+ hs->time,
+ &history_cb,
+ hs)))
+ TALER_TESTING_FAIL (is);
+}
+
+
+/**
+ * Make a "history" command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param ctx main CURL context
+ * @param http_status expected HTTP response code
+ * @param time FIXME
+ * @param nresult how many results are expected
+ * @param start FIXME.
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ struct GNUNET_TIME_Absolute time,
+ unsigned int nresult,
+ unsigned int start,
+ unsigned int nrows)
+{
+ struct HistoryState *hs;
+ struct TALER_TESTING_Command cmd;
+
+ 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->ctx = ctx;
+
+ cmd.cls = hs;
+ cmd.label = label;
+ cmd.run = &history_run;
+ cmd.cleanup = &history_cleanup;
+
+ return cmd;
+}
+
+/* end of testing_api_cmd_history.c */
diff --git a/src/lib/testing_api_cmd_pay.c b/src/lib/testing_api_cmd_pay.c
new file mode 100644
index 00000000..c7f3e878
--- /dev/null
+++ b/src/lib/testing_api_cmd_pay.c
@@ -0,0 +1,1538 @@
+/*
+ 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
+
+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 curl context; used to be fed to the merchant lib.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * 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;
+
+};
+
+
+struct CheckPaymentState
+{
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_MERCHANT_CheckPaymentOperation *cpo;
+
+ /**
+ * 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;
+
+ /**
+ * GNUNET_YES if we expect the proposal was paid.
+ */
+ unsigned int expect_paid;
+
+ /**
+ * The curl context; used to be fed to the merchant lib.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * The merchant base URL.
+ */
+ const char *merchant_url;
+
+};
+
+
+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;
+
+ /**
+ * Main CURL context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * 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;
+};
+
+
+struct PayAbortState
+{
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Reference to the "pay" command to abort.
+ */
+ const char *pay_reference;
+
+ /**
+ * Main CURL context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Merchant URL.
+ */
+ const char *merchant_url;
+
+ /**
+ * Handle to a "pay abort" operation.
+ */
+ struct TALER_MERCHANT_Pay *pao;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+
+ /**
+ * FIXME.
+ */
+ unsigned int num_refunds;
+
+ /**
+ * FIXME.
+ */
+ struct TALER_MERCHANT_RefundEntry *res;
+
+ /**
+ * FIXME.
+ */
+ struct GNUNET_HashCode h_contract;
+
+ /**
+ * Merchant public key.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+};
+
+
+struct PayAbortRefundState
+{
+ const char *abort_reference;
+
+ unsigned int num_coins;
+
+ const char *refund_amount;
+
+ const char *refund_fee;
+
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Handle to the refund operation.
+ */
+ struct TALER_EXCHANGE_RefundHandle *rh;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ struct TALER_EXCHANGE_Handle *exchange;
+};
+
+
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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);
+}
+
+
+/**
+ * Callback for GET /proposal issued at backend. Just check
+ * whether response code is as expected.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code we got
+ * @param json full response we got
+ */
+static void
+check_payment_cb (void *cls,
+ unsigned int http_status,
+ const json_t *obj,
+ int paid,
+ int refunded,
+ struct TALER_Amount *refund_amount,
+ const char *payment_redirect_url)
+{
+ struct CheckPaymentState *cps = cls;
+
+ cps->cpo = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "check payment: expected paid: %s: %d\n",
+ TALER_TESTING_interpreter_get_current_label (
+ cps->is),
+ cps->expect_paid);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "check payment: paid: %d\n",
+ paid);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "check payment: url: %s\n",
+ payment_redirect_url);
+
+ if (paid != cps->expect_paid)
+ TALER_TESTING_FAIL (cps->is);
+
+ if (cps->http_status != http_status)
+ TALER_TESTING_FAIL (cps->is);
+
+ TALER_TESTING_interpreter_next (cps->is);
+}
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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 (
+ cps->ctx,
+ cps->merchant_url,
+ "default", // only default instance for now.
+ order_id,
+ NULL,
+ NULL,
+ NULL,
+ check_payment_cb,
+ cps);
+
+ GNUNET_assert (NULL != cps->cpo);
+}
+
+/**
+ * Make a "check payment" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param ctx CURL context.
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *proposal_reference,
+ unsigned int expect_paid)
+{
+ struct CheckPaymentState *cps;
+ struct TALER_TESTING_Command cmd;
+
+ cps = GNUNET_new (struct CheckPaymentState);
+ cps->http_status = http_status;
+ cps->proposal_reference = proposal_reference;
+ cps->expect_paid = expect_paid;
+ cps->ctx = ctx;
+ cps->merchant_url = merchant_url;
+
+ cmd.cls = cps;
+ cmd.label = label;
+ cmd.run = &check_payment_run;
+ cmd.cleanup = &check_payment_cleanup;
+
+ return cmd;
+
+}
+
+/**
+ * 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
+ * @param amount_without_fee
+ * @param refund_fee
+ * @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;
+
+ /* 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];
+
+ struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+ struct TALER_DenominationSignature *denom_sig;
+ struct TALER_Amount *denom_value;
+
+ 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;
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_TESTING_get_trait_url
+ (coin_cmd, 0, &icoin->exchange_url));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_string_to_amount
+ (amount_with_fee, &icoin->amount_with_fee));
+
+ GNUNET_assert
+ (GNUNET_OK == TALER_string_to_amount
+ (amount_without_fee, &icoin->amount_without_fee));
+
+ 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.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200)
+ * for successful deposit; 0 if the exchange's reply is
+ * bogus (fails to follow the protocol)
+ * @param ec taler-specific error object
+ * @param obj the received JSON reply, should be kept as proof
+ * (and, in case of errors, be forwarded to the customer)
+ */
+static void
+pay_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *obj)
+{
+ struct PayState *ps = cls;
+
+ struct GNUNET_CRYPTO_EddsaSignature sig;
+ const char *error_name;
+ unsigned int error_line;
+ const struct GNUNET_CRYPTO_EddsaPublicKey *merchant_pub;
+ const struct TALER_TESTING_Command *proposal_cmd;
+
+ ps->po = NULL;
+ if (ps->http_status != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ http_status,
+ ec,
+ TALER_TESTING_interpreter_get_current_label (
+ ps->is));
+ TALER_TESTING_FAIL (ps->is);
+ }
+ if (MHD_HTTP_OK == 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 ()
+ };
+
+ GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (
+ obj, 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_peer_key_pub
+ (proposal_cmd, 0, &merchant_pub))
+ TALER_TESTING_FAIL (ps->is);
+
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (
+ TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
+ &mr.purpose, &sig,
+ merchant_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);
+}
+
+/**
+ * FIXME
+ */
+static void
+pay_abort_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct GNUNET_HashCode *h_contract,
+ unsigned int num_refunds,
+ const struct TALER_MERCHANT_RefundEntry *res,
+ const json_t *obj)
+{
+ struct PayAbortState *pas = cls;
+
+ pas->pao = NULL;
+ if (pas->http_status != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ http_status,
+ ec,
+ TALER_TESTING_interpreter_get_current_label
+ (pas->is));
+ TALER_TESTING_FAIL (pas->is);
+ }
+ if ( (MHD_HTTP_OK == http_status) &&
+ (TALER_EC_NONE == 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",
+ http_status);
+ TALER_TESTING_interpreter_next (pas->is);
+}
+
+
+/**
+ * @param cls the closure. Will be a "pay" or "pay-abort" state,
+ * depending on whether the caller was "pay" or "pay abort"
+ * run method.
+ *
+ * @return handle to the operation, NULL if errors occur.
+ */
+static struct TALER_MERCHANT_Pay *
+_pay_run (const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ 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,
+ struct TALER_MERCHANT_Pay * (*api_func) (),
+ void (*api_cb) (),
+ void *cls)
+{
+ const struct TALER_TESTING_Command *proposal_cmd;
+ const char *contract_terms;
+ json_t *ct;
+ 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;
+ }
+
+ json_error_t error;
+ if (NULL ==
+ (ct = json_loads (contract_terms,
+ JSON_COMPACT,
+ &error)))
+ {
+ 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 (ct,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u\n",
+ error_name,
+ error_line);
+ fprintf (stderr, "%s\n", contract_terms);
+ 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 = api_func (ctx,
+ merchant_url,
+ "default", // instance
+ 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,
+ cls);
+
+ GNUNET_array_grow (pay_coins,
+ npay_coins,
+ 0);
+ return ret;
+}
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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->ctx,
+ ps->coin_reference,
+ ps->proposal_reference,
+ is,
+ ps->amount_with_fee,
+ ps->amount_without_fee,
+ ps->refund_fee,
+ &TALER_MERCHANT_pay_wallet,
+ &pay_cb,
+ ps)) )
+ TALER_TESTING_FAIL (is);
+}
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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);
+}
+
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ * to return in case there were multiple generated
+ * by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+pay_traits (void *cls,
+ void **ret,
+ const char *trait,
+ unsigned int index)
+{
+
+ struct PayState *ps = cls;
+ const char *order_id;
+ const struct TALER_TESTING_Command *proposal_cmd;
+ struct GNUNET_CRYPTO_EddsaPublicKey *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_peer_key_pub
+ (proposal_cmd,
+ 0,
+ (const struct GNUNET_CRYPTO_EddsaPublicKey **)
+ &merchant_pub))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_amount
+ (AMOUNT_WITH_FEE, ps->amount_with_fee),
+ TALER_TESTING_make_trait_amount
+ (AMOUNT_WITHOUT_FEE, ps->amount_without_fee),
+ TALER_TESTING_make_trait_amount
+ (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_peer_key_pub (0, merchant_pub),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+
+ return GNUNET_SYSERR;
+}
+
+/**
+ * Make a "pay" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param ctx CURL context.
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ 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;
+ struct TALER_TESTING_Command cmd;
+
+ ps = GNUNET_new (struct PayState);
+ ps->http_status = http_status;
+ ps->proposal_reference = proposal_reference;
+ ps->coin_reference = coin_reference;
+ ps->ctx = ctx;
+ ps->merchant_url = merchant_url;
+ ps->amount_with_fee = amount_with_fee;
+ ps->amount_without_fee = amount_without_fee;
+ ps->refund_fee = refund_fee;
+
+ cmd.cls = ps;
+ cmd.label = label;
+ cmd.run = &pay_run;
+ cmd.cleanup = &pay_cleanup;
+ cmd.traits = &pay_traits;
+
+ return cmd;
+
+}
+
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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 (pas);
+}
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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_amount
+ (pay_cmd, AMOUNT_WITH_FEE, &amount_with_fee))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_amount
+ (pay_cmd, AMOUNT_WITHOUT_FEE, &amount_without_fee))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_amount
+ (pay_cmd, REFUND_FEE, &refund_fee))
+ TALER_TESTING_FAIL (is);
+
+ if ( NULL ==
+ ( pas->pao = _pay_run (pas->merchant_url,
+ pas->ctx,
+ coin_reference,
+ proposal_reference,
+ is,
+ amount_with_fee,
+ amount_without_fee,
+ refund_fee,
+ &TALER_MERCHANT_pay_abort,
+ &pay_abort_cb,
+ pas)) )
+ TALER_TESTING_FAIL (is);
+}
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ * to return in case there were multiple generated
+ * by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+pay_abort_traits (void *cls,
+ void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PayAbortState *pas = cls;
+
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_peer_key_pub
+ (0, &pas->merchant_pub.eddsa_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);
+
+ return GNUNET_SYSERR;
+}
+
+/**
+ * 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 ctx main CURL context
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status)
+{
+ struct PayAbortState *pas;
+ struct TALER_TESTING_Command cmd;
+
+ pas = GNUNET_new (struct PayAbortState);
+ pas->http_status = http_status;
+ pas->pay_reference = pay_reference;
+ pas->ctx = ctx;
+ pas->merchant_url = merchant_url;
+
+ cmd.cls = pas;
+ cmd.label = label;
+ cmd.run = &pay_abort_run;
+ cmd.cleanup = &pay_abort_cleanup;
+ cmd.traits = &pay_abort_traits;
+
+ return cmd;
+}
+
+/**
+ * Function called with the result of a /pay again operation.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200)
+ * for successful deposit; 0 if the exchange's reply is
+ * bogus (fails to follow the protocol)
+ * @param ec taler-specific error object
+ * @param obj the received JSON reply, should be kept as proof
+ * (and, in case of errors, be forwarded to the customer)
+ */
+static void
+pay_again_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *obj)
+{
+ 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 GNUNET_CRYPTO_EddsaPublicKey *merchant_pub;
+
+ pas->pao = NULL;
+ if (pas->http_status != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ http_status,
+ 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 == 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 (obj,
+ 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_peer_key_pub
+ (pay_cmd, 0, &merchant_pub))
+ TALER_TESTING_FAIL (pas->is);
+
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify
+ (TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
+ &mr.purpose,
+ &sig,
+ merchant_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);
+}
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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_amount
+ (pay_cmd, AMOUNT_WITH_FEE, &amount_with_fee))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_amount
+ (pay_cmd, AMOUNT_WITHOUT_FEE, &amount_without_fee))
+ TALER_TESTING_FAIL (is);
+
+ if ( NULL ==
+ ( pas->pao = _pay_run (pas->merchant_url,
+ pas->ctx,
+ pas->coin_reference,
+ proposal_reference,
+ is,
+ amount_with_fee,
+ amount_without_fee,
+ pas->refund_fee,
+ &TALER_MERCHANT_pay_wallet,
+ &pay_again_cb,
+ pas)) )
+ TALER_TESTING_FAIL (is);
+}
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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.
+ *
+ * @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 ctx main CURL context
+ * @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,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status)
+{
+
+ struct PayAgainState *pas;
+ struct TALER_TESTING_Command cmd;
+
+ pas = GNUNET_new (struct PayAgainState);
+ pas->http_status = http_status;
+ pas->pay_reference = pay_reference;
+ pas->coin_reference = coin_reference;
+ pas->ctx = ctx;
+ pas->merchant_url = merchant_url;
+ pas->refund_fee = refund_fee;
+
+ cmd.cls = pas;
+ cmd.label = label;
+ cmd.run = &pay_again_run;
+ cmd.cleanup = &pay_again_cleanup;
+
+ return cmd;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of
+ * submitting a refund request to an exchange.
+ *
+ * @param cls closure
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for
+ * successful deposit; 0 if the exchange's reply is bogus
+ * (fails to follow the protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param sign_key exchange key used to sign @a obj, or NULL
+ * @param obj the received JSON reply, should be kept as proof
+ * (and, in particular, be forwarded to the customer)
+ */
+static void
+abort_refund_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const struct TALER_ExchangePublicKeyP *sign_key,
+ const json_t *obj)
+{
+ struct PayAbortRefundState *pars = cls;
+
+ pars->rh = NULL;
+ if (pars->http_status != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ http_status,
+ ec,
+ TALER_TESTING_interpreter_get_current_label
+ (pars->is));
+ TALER_TESTING_interpreter_fail (pars->is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (pars->is);
+}
+
+/**
+ * FIXME.
+ */
+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);
+}
+
+/**
+ * FIXME.
+ */
+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;
+ unsigned int *num_refunds;
+ const struct TALER_TESTING_Command *abort_cmd;
+ const struct GNUNET_CRYPTO_EddsaPublicKey *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_peer_key_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
+ (pars->exchange,
+ &refund_amount,
+ &refund_fee,
+ h_contract_terms,
+ &refund_entry->coin_pub,
+ refund_entry->rtransaction_id,
+ (const struct TALER_MerchantPublicKeyP *) merchant_pub,
+ &refund_entry->merchant_sig,
+ &abort_refund_cb,
+ pars);
+
+ GNUNET_assert (NULL != pars->rh);
+}
+
+
+/**
+ * FIXME.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_abort_refund
+ (const char *label,
+ struct TALER_EXCHANGE_Handle *exchange,
+ const char *abort_reference,
+ unsigned int num_coins,
+ const char *refund_amount,
+ const char *refund_fee,
+ unsigned int http_status)
+{
+ struct PayAbortRefundState *pars;
+ struct TALER_TESTING_Command cmd;
+
+ 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;
+ pars->exchange = exchange;
+
+ cmd.cls = pars;
+ cmd.label = label;
+ cmd.run = &pay_abort_refund_run;
+ cmd.cleanup = &pay_abort_refund_cleanup;
+
+ return cmd;
+}
+
+/* end of testing_api_cmd_pay.c */
diff --git a/src/lib/testing_api_cmd_proposal.c b/src/lib/testing_api_cmd_proposal.c
new file mode 100644
index 00000000..3c6c439c
--- /dev/null
+++ b/src/lib/testing_api_cmd_proposal.c
@@ -0,0 +1,560 @@
+/*
+ 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_exec_merchant.c
+ * @brief command to execute the merchant backend service.
+ * @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"
+
+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.
+ */
+ const char *contract_terms;
+
+ /**
+ * Proposal data hash code. Recall: proposal data is the part
+ * of the contract terms without the signature of the merchant.
+ */
+ struct GNUNET_HashCode h_contract_terms;
+
+ /**
+ * The /proposal operation handle.
+ */
+ struct TALER_MERCHANT_ProposalOperation *po;
+
+ /**
+ * The (initial) /proposal/lookup operation handle.
+ */
+ struct TALER_MERCHANT_ProposalLookupOperation *plo;
+
+ /**
+ * The nonce.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey nonce;
+
+ /**
+ * The merchant instance.
+ */
+ const char *instance;
+
+ /**
+ * URL of the merchant backend.
+ */
+ const char *merchant_url;
+
+ /**
+ * The curl context; used to be fed to the merchant lib.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * 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;
+};
+
+struct ProposalLookupState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * URL of the merchant backend.
+ */
+ const char *merchant_url;
+
+ /**
+ * The curl context; used to be fed to the merchant lib.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * The (initial) /proposal/lookup operation handle.
+ */
+ struct TALER_MERCHANT_ProposalLookupOperation *plo;
+
+ /**
+ * Reference to a proposal operation.
+ */
+ const char *proposal_reference;
+};
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ * to return in case there were multiple generated
+ * by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+proposal_traits (void *cls,
+ void **ret,
+ const char *trait,
+ unsigned int index)
+{
+
+ struct ProposalState *ps = cls;
+ #define MAKE_TRAIT_NONCE(ptr) \
+ TALER_TESTING_make_trait_peer_key_pub (1, 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_peer_key_pub
+ (0, &ps->merchant_pub.eddsa_pub),
+ MAKE_TRAIT_NONCE (&ps->nonce),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Used to initialize the proposal after it was created.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code we got
+ * @param json full response we got
+ */
+static void
+proposal_lookup_initial_cb
+ (void *cls,
+ unsigned int http_status,
+ const json_t *json,
+ 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;
+
+ ps->plo = NULL;
+ if (ps->http_status != http_status)
+ TALER_TESTING_FAIL (ps->is);
+
+ ps->contract_terms = json_dumps (contract_terms,
+ JSON_COMPACT);
+ ps->h_contract_terms = *hash;
+ ps->merchant_sig = *sig;
+
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract_terms,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u\n",
+ error_name,
+ error_line);
+ fprintf (stderr, "%s\n", ps->contract_terms);
+ 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 http_status HTTP response code coming from
+ * the backend.
+ * @param ec error code.
+ * @param obj when successful, it matches the format:
+ * '{"order_id": "<order_id>"}'
+ */
+
+static void
+proposal_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *obj,
+ const char *order_id)
+{
+ struct ProposalState *ps = cls;
+
+ ps->po = NULL;
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ ps->order_id = GNUNET_strdup (order_id);
+ break;
+ default:
+ {
+ char *s = json_dumps (obj, JSON_COMPACT);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected status code from /proposal:" \
+ " %u (%d). Command %s, response: %s\n",
+ http_status,
+ ec,
+ TALER_TESTING_interpreter_get_current_label (
+ ps->is),
+ s);
+ GNUNET_free_non_null (s);
+ TALER_TESTING_interpreter_fail (ps->is);
+ }
+ return;
+ }
+
+
+ if (NULL ==
+ (ps->plo = TALER_MERCHANT_proposal_lookup
+ (ps->ctx,
+ ps->merchant_url,
+ ps->order_id,
+ "default",
+ &ps->nonce,
+ &proposal_lookup_initial_cb,
+ ps)))
+ TALER_TESTING_FAIL (ps->is);
+}
+
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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;
+ }
+
+ GNUNET_CRYPTO_random_block
+ (GNUNET_CRYPTO_QUALITY_WEAK,
+ &ps->nonce,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ if (NULL != ps->instance)
+ {
+ json_t *merchant;
+ merchant = json_object ();
+ json_object_set_new (merchant,
+ "instance",
+ json_string (ps->instance));
+ json_object_set_new (order,
+ "merchant",
+ merchant);
+ }
+
+ ps->po = TALER_MERCHANT_order_put (ps->ctx,
+ ps->merchant_url,
+ order,
+ &proposal_cb,
+ ps);
+ json_decref (order);
+ GNUNET_assert (NULL != ps->po);
+}
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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;
+ }
+
+ GNUNET_free ((void *) ps->order_id);
+ GNUNET_free ((void *) ps->contract_terms);
+ GNUNET_free (ps);
+}
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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);
+ }
+ GNUNET_free (pls);
+}
+
+
+/**
+ * Make the /proposal command.
+ *
+ * @param label command label
+ * @param merchant_reference label to the merchant command. Used
+ * to get its base url.
+ * @param ctx context
+ * @param http_status HTTP status code.
+ * @param order the order
+ * @param instance the merchant instance
+ * @param merchant_url the merchant backend (base) url
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *order,
+ const char *instance)
+{
+ struct TALER_TESTING_Command cmd;
+ struct ProposalState *ps;
+
+ ps = GNUNET_new (struct ProposalState);
+ ps->order = order;
+ ps->http_status = http_status;
+ ps->ctx = ctx;
+ ps->merchant_url = merchant_url;
+
+ cmd.cls = ps;
+ cmd.label = label;
+ cmd.run = &proposal_run;
+ cmd.cleanup = &proposal_cleanup;
+ cmd.traits = &proposal_traits;
+ return cmd;
+}
+
+/**
+ * Callback for GET /proposal issued at backend. Just check
+ * whether response code is as expected.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code we got
+ * @param json full response we got
+ */
+static void
+proposal_lookup_cb (void *cls,
+ unsigned int http_status,
+ const json_t *json,
+ 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 != http_status)
+ TALER_TESTING_FAIL (pls->is);
+
+ TALER_TESTING_interpreter_next (pls->is);
+}
+
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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 struct TALER_TESTING_Command *proposal_cmd;
+ const char *order_id;
+ const struct GNUNET_CRYPTO_EddsaPublicKey *nonce;
+ #define GET_TRAIT_NONCE(cmd,ptr) \
+ TALER_TESTING_get_trait_peer_key_pub (cmd, 1, ptr)
+
+ pls->is = is;
+ 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))
+ TALER_TESTING_FAIL (is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_order_id
+ (proposal_cmd, 0, &order_id))
+ TALER_TESTING_FAIL (is);
+
+ pls->plo = TALER_MERCHANT_proposal_lookup (pls->ctx,
+ pls->merchant_url,
+ order_id,
+ "default",
+ nonce,
+ &proposal_lookup_cb,
+ pls);
+ GNUNET_assert (NULL != pls->plo);
+}
+
+
+
+/**
+ * Make a "proposal lookup" command.
+ *
+ * @param label command label
+ * @param
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal_lookup
+ (const char *label,
+ struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference)
+{
+ struct ProposalLookupState *pls;
+ struct TALER_TESTING_Command cmd;
+
+ pls = GNUNET_new (struct ProposalLookupState);
+ pls->http_status = http_status;
+ pls->proposal_reference = proposal_reference;
+ pls->merchant_url = merchant_url;
+ pls->ctx = ctx;
+
+ cmd.cls = pls;
+ cmd.label = label;
+ cmd.run = &proposal_lookup_run;
+ cmd.cleanup = &proposal_lookup_cleanup;
+
+ return cmd;
+}
diff --git a/src/lib/testing_api_cmd_refund.c b/src/lib/testing_api_cmd_refund.c
new file mode 100644
index 00000000..b85dcf68
--- /dev/null
+++ b/src/lib/testing_api_cmd_refund.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.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"
+
+
+struct RefundIncreaseState
+{
+ struct TALER_MERCHANT_RefundIncreaseOperation *rio;
+
+ const char *merchant_url;
+
+ struct GNUNET_CURL_Context *ctx;
+
+ const char *order_id;
+
+ const char *refund_amount;
+
+ const char *refund_fee;
+
+ const char *reason;
+
+ struct TALER_TESTING_Interpreter *is;
+};
+
+struct RefundLookupState
+{
+ struct TALER_MERCHANT_RefundLookupOperation *rlo;
+
+ const char *merchant_url;
+
+ struct GNUNET_CURL_Context *ctx;
+
+ const char *order_id;
+
+ const char *pay_reference;
+
+ const char *increase_reference;
+
+ struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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);
+}
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+refund_lookup_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ /* FIXME: make sure no other data must be free'd */
+ 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 POST /refund (increase) response
+ *
+ * @param cls closure
+ * @param http_status HTTP status code
+ * @param ec taler-specific error object
+ * @param obj response body; is NULL on success.
+ */
+static void
+refund_increase_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *obj)
+{
+ struct RefundIncreaseState *ris = cls;
+
+ ris->rio = NULL;
+ if (MHD_HTTP_OK != http_status)
+ TALER_TESTING_FAIL (ris->is);
+
+ TALER_TESTING_interpreter_next (ris->is);
+}
+
+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 (ris->ctx,
+ ris->merchant_url,
+ ris->order_id,
+ &refund_amount,
+ ris->reason,
+ "default",
+ &refund_increase_cb,
+ ris);
+ GNUNET_assert (NULL != ris->rio);
+}
+
+/**
+ * Callback that frees all the elements in the hashmap
+ *
+ * @param cls closure, NULL
+ * @param key current key
+ * @param value a `struct TALER_Amount`
+ * @return always #GNUNET_YES (continue to iterate)
+ */
+static int
+hashmap_free (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct TALER_Amount *refund_amount = value;
+
+ GNUNET_free (refund_amount);
+ return GNUNET_YES;
+}
+
+
+/**
+ * Process GET /refund (increase) response.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code
+ * @param ec taler-specific error object
+ * @param obj response body; is NULL on error.
+ */
+static void
+refund_lookup_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *obj)
+{
+ struct RefundLookupState *rls = cls;
+ struct GNUNET_CONTAINER_MultiHashMap *map;
+ size_t index;
+ json_t *elem;
+ const char *error_name;
+ unsigned int error_line;
+ struct GNUNET_HashCode h_coin_pub;
+ const char *coin_reference;
+ char *coin_reference_dup;
+ const char *icoin_reference;
+ const struct TALER_TESTING_Command *pay_cmd;
+ const struct TALER_TESTING_Command *icoin_cmd;
+ const struct TALER_TESTING_Command *increase_cmd;
+ const char *refund_amount;
+ struct TALER_Amount acc;
+ struct TALER_Amount ra;
+ const json_t *arr;
+
+ rls->rlo = NULL;
+ if (MHD_HTTP_OK != http_status)
+ TALER_TESTING_FAIL (rls->is);
+
+ map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+ arr = json_object_get (obj, "refund_permissions");
+ if (NULL == arr)
+ TALER_TESTING_FAIL (rls->is);
+
+ json_array_foreach (arr, index, elem)
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_Amount *irefund_amount = GNUNET_new
+ (struct TALER_Amount);
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
+ TALER_JSON_spec_amount ("refund_amount", irefund_amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (elem,
+ spec,
+ &error_name,
+ &error_line));
+ GNUNET_CRYPTO_hash (&coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ &h_coin_pub);
+ GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put
+ (map,
+ &h_coin_pub,
+ irefund_amount,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ };
+
+ if ( NULL ==
+ ( pay_cmd = TALER_TESTING_interpreter_lookup_command
+ (rls->is, rls->pay_reference)))
+ TALER_TESTING_FAIL (rls->is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_coin_reference
+ (pay_cmd, 0, &coin_reference))
+ TALER_TESTING_FAIL (rls->is);
+
+ GNUNET_assert (GNUNET_OK == TALER_amount_get_zero ("EUR",
+ &acc));
+ coin_reference_dup = GNUNET_strdup (coin_reference);
+ for (icoin_reference = strtok (coin_reference_dup, ";");
+ NULL != icoin_reference;
+ icoin_reference = strtok (NULL, ";"))
+ {
+ struct TALER_CoinSpendPrivateKeyP *icoin_priv;
+ struct TALER_CoinSpendPublicKeyP icoin_pub;
+ struct GNUNET_HashCode h_icoin_pub;
+ struct TALER_Amount *iamount;
+
+ 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);
+ 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);
+ return;
+ }
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&icoin_priv->eddsa_priv,
+ &icoin_pub.eddsa_pub);
+ GNUNET_CRYPTO_hash (&icoin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ &h_icoin_pub);
+
+ iamount = GNUNET_CONTAINER_multihashmap_get
+ (map, &h_icoin_pub);
+
+ /* Can be NULL: not all coins are involved in refund */
+ if (NULL == iamount)
+ continue;
+
+ GNUNET_assert (GNUNET_OK == TALER_amount_add (&acc,
+ &acc,
+ iamount));
+ }
+
+ GNUNET_free (coin_reference_dup);
+
+ if ( NULL ==
+ ( increase_cmd = TALER_TESTING_interpreter_lookup_command
+ (rls->is, rls->increase_reference)))
+ TALER_TESTING_FAIL (rls->is);
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_amount
+ (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);
+
+ GNUNET_CONTAINER_multihashmap_iterate (map,
+ &hashmap_free,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (map);
+
+ if (0 != TALER_amount_cmp (&acc,
+ &ra))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Incomplete refund: expected '%s', got '%s'\n",
+ refund_amount,
+ TALER_amount_to_string (&acc));
+ TALER_TESTING_interpreter_fail (rls->is);
+ return;
+ }
+
+ TALER_TESTING_interpreter_next (rls->is);
+}
+
+
+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 (rls->ctx,
+ rls->merchant_url,
+ rls->order_id,
+ "default",
+ &refund_lookup_cb,
+ rls);
+ GNUNET_assert (NULL != rls->rlo);
+}
+
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ * to return in case there were multiple generated
+ * by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+refund_increase_traits (void *cls,
+ void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RefundIncreaseState *ris = cls;
+
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_amount (0, ris->refund_amount),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+
+ return GNUNET_SYSERR;
+}
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_increase
+ (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ const char *reason,
+ const char *order_id,
+ const char *refund_amount,
+ const char *refund_fee)
+{
+ struct RefundIncreaseState *ris;
+ struct TALER_TESTING_Command cmd;
+
+ ris = GNUNET_new (struct RefundIncreaseState);
+ ris->merchant_url = merchant_url;
+ ris->ctx = ctx;
+ ris->order_id = order_id;
+ ris->refund_amount = refund_amount;
+ ris->refund_fee = refund_fee;
+ ris->reason = reason;
+
+ cmd.cls = ris;
+ cmd.label = label;
+ cmd.run = &refund_increase_run;
+ cmd.cleanup = &refund_increase_cleanup;
+ cmd.traits = &refund_increase_traits;
+
+ return cmd;
+}
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_lookup
+ (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ const char *increase_reference,
+ const char *pay_reference,
+ const char *order_id)
+{
+ struct RefundLookupState *rls;
+ struct TALER_TESTING_Command cmd;
+
+ rls = GNUNET_new (struct RefundLookupState);
+ rls->merchant_url = merchant_url;
+ rls->ctx = ctx;
+ rls->order_id = order_id;
+ rls->pay_reference = pay_reference;
+ rls->increase_reference = increase_reference;
+
+ cmd.cls = rls;
+ cmd.label = label;
+ cmd.run = &refund_lookup_run;
+ cmd.cleanup = &refund_lookup_cleanup;
+
+ return cmd;
+}
+
+
+
+/* end of testing_api_cmd_refund.c */
diff --git a/src/lib/testing_api_cmd_tip.c b/src/lib/testing_api_cmd_tip.c
new file mode 100644
index 00000000..111180a4
--- /dev/null
+++ b/src/lib/testing_api_cmd_tip.c
@@ -0,0 +1,777 @@
+/*
+ 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"
+
+
+struct TipPickupState
+{
+ const char *merchant_url;
+
+ struct GNUNET_CURL_Context *ctx;
+
+ unsigned int http_status;
+
+ const char *authorize_reference;
+
+ /**
+ * Set to non-NULL to a label of another pick up operation
+ * that we should replay.
+ */
+ const char *replay_reference;
+
+ struct TALER_MERCHANT_TipPickupOperation *tpo;
+
+ struct TALER_TESTING_Interpreter *is;
+
+ const char **amounts;
+
+ unsigned int num_coins;
+
+ const struct TALER_EXCHANGE_DenomPublicKey **dks;
+
+ struct TALER_PlanchetSecretsP *psa;
+
+ /**
+ * Temporary data structure of @e num_coins entries for the
+ * withdraw operations.
+ */
+ struct WithdrawHandle *withdraws;
+
+ /**
+ * Set (by the interpreter) to an array of @a num_coins
+ * signatures created from the (successful) tip operation.
+ */
+ struct TALER_DenominationSignature *sigs;
+
+ enum TALER_ErrorCode expected_ec;
+
+ struct TALER_EXCHANGE_Handle *exchange;
+};
+
+
+struct TipQueryState
+{
+ const char *merchant_url;
+
+ struct GNUNET_CURL_Context *ctx;
+
+ unsigned int http_status;
+
+ const char *instance;
+
+ struct TALER_MERCHANT_TipQueryOperation *tqo;
+
+ struct TALER_TESTING_Interpreter *is;
+
+ const char *expected_amount_picked_up;
+
+ const char *expected_amount_authorized;
+
+ const char *expected_amount_available;
+};
+
+
+struct TipAuthorizeState
+{
+ const char *merchant_url;
+
+ struct GNUNET_CURL_Context *ctx;
+
+ unsigned int http_status;
+
+ const char *instance;
+
+ const char *justification;
+
+ const char *amount;
+
+ enum TALER_ErrorCode expected_ec;
+
+ const char *exchange_url;
+
+ struct GNUNET_HashCode tip_id;
+
+ struct GNUNET_TIME_Absolute tip_expiration;
+
+ struct TALER_MERCHANT_TipAuthorizeOperation *tao;
+
+ struct TALER_TESTING_Interpreter *is;
+};
+
+/**
+ * Callback for a /tip-authorize request. Returns the result
+ * of the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant backend
+ * @param ec taler-specific error code
+ * @param tip_id which tip ID should be used to pickup the tip
+ * @param tip_expiration when does the tip expire (needs to be
+ * picked up before this time)
+ * @param exchange_url at what exchange can the tip be picked up
+ */
+static void
+tip_authorize_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const struct GNUNET_HashCode *tip_id,
+ struct GNUNET_TIME_Absolute tip_expiration,
+ const char *exchange_url)
+{
+ struct TipAuthorizeState *tas = cls;
+
+ tas->tao = NULL;
+ if (tas->http_status != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d)"
+ " to command %s\n",
+ http_status,
+ ec,
+ TALER_TESTING_interpreter_get_current_label
+ (tas->is));
+
+ TALER_TESTING_interpreter_fail (tas->is);
+ return;
+ }
+
+ if (tas->expected_ec != ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error code %d (%u) to command %s\n",
+ ec,
+ http_status,
+ TALER_TESTING_interpreter_get_current_label
+ (tas->is));
+ TALER_TESTING_interpreter_fail (tas->is);
+ return;
+ }
+ if ( (MHD_HTTP_OK == http_status) &&
+ (TALER_EC_NONE == ec) )
+ {
+ if (0 != strcmp (exchange_url,
+ tas->exchange_url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected exchange URL %s to command %s\n",
+ exchange_url,
+ TALER_TESTING_interpreter_get_current_label
+ (tas->is));
+ TALER_TESTING_interpreter_fail (tas->is);
+ return;
+ }
+ tas->tip_id = *tip_id;
+ tas->tip_expiration = tip_expiration;
+ }
+
+ TALER_TESTING_interpreter_next (tas->is);
+}
+
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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
+ (tas->ctx,
+ tas->merchant_url,
+ "http://merchant.com/pickup",
+ "http://merchant.com/continue",
+ &amount,
+ tas->instance,
+ tas->justification,
+ tip_authorize_cb,
+ tas);
+
+ GNUNET_assert (NULL != tas->tao);
+}
+
+
+/**
+ * FIXME
+ */
+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);
+}
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *instance,
+ const char *justification,
+ const char *amount)
+{
+ struct TipAuthorizeState *tas;
+ struct TALER_TESTING_Command cmd;
+
+ tas = GNUNET_new (struct TipAuthorizeState);
+ tas->merchant_url = merchant_url;
+ tas->ctx = ctx;
+ tas->instance = instance;
+ tas->justification = justification;
+ tas->amount = amount;
+ tas->http_status = http_status;
+
+ cmd.label = label;
+ cmd.cls = tas;
+ cmd.run = &tip_authorize_run;
+ cmd.cleanup = &tip_authorize_cleanup;
+
+ return cmd;
+}
+
+/**
+ * Callback to process a GET /tip-query request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec Taler-specific error code
+ * @param raw raw response body
+ */
+static void
+tip_query_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *raw,
+ 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));
+ TALER_LOG_INFO ("expected available %s, actual %s\n",
+ TALER_amount_to_string (&a),
+ TALER_amount_to_string (amount_available));
+ if (0 != TALER_amount_cmp (amount_available, &a))
+ TALER_TESTING_FAIL (tqs->is);
+ }
+
+ if (tqs->expected_amount_authorized)
+ {
+ GNUNET_assert (GNUNET_OK == TALER_string_to_amount
+ (tqs->expected_amount_authorized, &a));
+ TALER_LOG_INFO ("expected authorized %s, actual %s\n",
+ TALER_amount_to_string (&a),
+ TALER_amount_to_string (amount_authorized));
+ if (0 != TALER_amount_cmp (amount_authorized, &a))
+ TALER_TESTING_FAIL (tqs->is);
+ }
+
+ if (tqs->expected_amount_picked_up)
+ {
+ GNUNET_assert (GNUNET_OK == TALER_string_to_amount
+ (tqs->expected_amount_picked_up, &a));
+ TALER_LOG_INFO ("expected picked_up %s, actual %s\n",
+ TALER_amount_to_string (&a),
+ TALER_amount_to_string (amount_picked_up));
+ if (0 != TALER_amount_cmp (amount_picked_up, &a))
+ TALER_TESTING_FAIL (tqs->is);
+ }
+
+ if (tqs->http_status != http_status)
+ TALER_TESTING_FAIL (tqs->is);
+
+ TALER_TESTING_interpreter_next (tqs->is);
+}
+
+/**
+ * FIXME
+ */
+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);
+}
+
+/**
+ * FIXME
+ */
+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 (tqs->ctx,
+ tqs->merchant_url,
+ tqs->instance,
+ &tip_query_cb,
+ tqs);
+ GNUNET_assert (NULL != tqs->tqo);
+}
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_query (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *instance)
+{
+ struct TipQueryState *tqs;
+ struct TALER_TESTING_Command cmd;
+
+ tqs = GNUNET_new (struct TipQueryState);
+ tqs->merchant_url = merchant_url;
+ tqs->ctx = ctx;
+ tqs->instance = instance;
+
+ cmd.cls = tqs;
+ cmd.label = label;
+ cmd.run = &tip_query_run;
+ cmd.cleanup = &tip_query_cleanup;
+
+ return cmd;
+}
+
+/**
+ * Internal withdraw handle used when withdrawing tips.
+ */
+struct WithdrawHandle
+{
+ /**
+ * Withdraw operation this handle represents.
+ */
+ struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh;
+
+ /**
+ * Interpreter state we are part of.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Offset of this withdraw operation in the current
+ * @e is command.
+ */
+ unsigned int off;
+
+};
+
+/**
+ * Callbacks of this type are used to serve the result of
+ * submitting a withdraw request to a exchange.
+ *
+ * @param cls closure, a `struct WithdrawHandle *`
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200)
+ * for successful status request, 0 if the exchange's
+ * reply is bogus (fails to follow the protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param sig signature over the coin, NULL on error
+ * @param full_response full response from the exchange
+ * (for logging, in case of errors)
+ */
+static void
+pickup_withdraw_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const struct TALER_DenominationSignature *sig,
+ const json_t *full_response)
+{
+ struct WithdrawHandle *wh = cls;
+ struct TALER_TESTING_Interpreter *is = wh->is;
+ struct TipPickupState *tps = is->commands[is->ip].cls;
+
+ wh->wsh = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Withdraw operation %u completed with %u (%d)\n",
+ wh->off,
+ http_status,
+ ec);
+ GNUNET_assert (wh->off < tps->num_coins);
+ if ( (MHD_HTTP_OK != http_status) ||
+ (TALER_EC_NONE != ec) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d)"
+ " to command %s when withdrawing\n",
+ http_status,
+ ec,
+ TALER_TESTING_interpreter_get_current_label (is));
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (NULL == tps->sigs)
+ tps->sigs = GNUNET_new_array
+ (tps->num_coins, struct TALER_DenominationSignature);
+
+ GNUNET_assert (NULL == tps->sigs[wh->off].rsa_signature);
+ tps->sigs[wh->off].rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature);
+
+ for (unsigned int i=0; i<tps->num_coins; i++)
+ if (NULL != tps->withdraws[wh->off].wsh)
+ return; /* still some ops ongoing */
+
+ GNUNET_free (tps->withdraws);
+ tps->withdraws = NULL;
+ TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Callback for a /tip-pickup request. Returns the result of
+ * the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant
+ * backend, "200 OK" on success
+ * @param ec taler-specific error code
+ * @param reserve_pub public key of the reserve that made the
+ * @a reserve_sigs, NULL on error
+ * @param num_reserve_sigs length of the @a reserve_sigs array,
+ * 0 on error
+ * @param reserve_sigs array of signatures authorizing withdrawals,
+ * NULL on error
+ * @param json original json response
+ */
+static void
+pickup_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ unsigned int num_reserve_sigs,
+ const struct TALER_ReserveSignatureP *reserve_sigs,
+ const json_t *json)
+{
+ struct TipPickupState *tps = cls;
+
+ tps->tpo = NULL;
+ if (http_status != tps->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ http_status,
+ ec,
+ TALER_TESTING_interpreter_get_current_label
+ (tps->is));
+ TALER_TESTING_FAIL (tps->is);
+ }
+
+ if (ec != tps->expected_ec)
+ TALER_TESTING_FAIL (tps->is);
+
+
+ if ( (MHD_HTTP_OK != http_status) ||
+ (TALER_EC_NONE != ec) )
+ {
+ TALER_TESTING_interpreter_next (tps->is);
+ return;
+ }
+ if (num_reserve_sigs != tps->num_coins)
+ TALER_TESTING_FAIL (tps->is);
+
+
+ /* pickup successful, now withdraw! */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Obtained %u signatures for withdrawal"
+ " from picking up a tip\n",
+ num_reserve_sigs);
+ GNUNET_assert (NULL == tps->withdraws);
+ tps->withdraws = GNUNET_new_array
+ (num_reserve_sigs, struct WithdrawHandle);
+
+ for (unsigned int i=0;i<num_reserve_sigs;i++)
+ {
+ struct WithdrawHandle *wh = &tps->withdraws[i];
+
+ wh->off = i;
+ wh->is = tps->is;
+ GNUNET_assert
+ ( (NULL == wh->wsh) &&
+ ( (NULL == tps->sigs) ||
+ (NULL == tps->sigs[wh->off].rsa_signature) ) );
+ wh->wsh = TALER_EXCHANGE_reserve_withdraw2
+ (tps->exchange,
+ tps->dks[i],
+ &reserve_sigs[i],
+ reserve_pub,
+ &tps->psa[i],
+ &pickup_withdraw_cb,
+ wh);
+ if (NULL == wh->wsh)
+ TALER_TESTING_FAIL (tps->is);
+ }
+ if (0 == num_reserve_sigs)
+ TALER_TESTING_interpreter_next (tps->is);
+}
+
+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;
+ struct TALER_Amount amount;
+ const struct GNUNET_HashCode *tip_id;
+
+ tps->is = is;
+ if (NULL == tps->replay_reference)
+ {
+ replay_cmd = NULL;
+
+ /* Count planchets. */
+ for (num_planchets=0;
+ NULL != tps->amounts[num_planchets];
+ num_planchets++);
+ }
+ else
+ {
+ 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_PlanchetDetail 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 *);
+
+ for (unsigned int i=0;i<num_planchets;i++)
+ {
+ if (NULL == replay_cmd)
+ {
+ GNUNET_assert (GNUNET_OK == TALER_string_to_amount
+ (tps->amounts[i], &amount));
+
+ tps->dks[i] = TALER_TESTING_find_pk (is->keys,
+ &amount);
+ if (NULL == tps->dks[i])
+ TALER_TESTING_FAIL (is);
+
+ TALER_planchet_setup_random (&tps->psa[i]);
+ }
+ else
+ {
+ if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub
+ (replay_cmd, i, &tps->dks[i]))
+ TALER_TESTING_FAIL (is);
+
+ struct TALER_PlanchetSecretsP *ps;
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_planchet_secrets
+ (replay_cmd, i, &ps))
+ TALER_TESTING_FAIL (is);
+ tps->psa[i] = *ps;
+ }
+
+ if (GNUNET_OK != TALER_planchet_prepare (&tps->dks[i]->key,
+ &tps->psa[i],
+ &planchets[i]))
+ TALER_TESTING_FAIL (is);
+ }
+
+ if (GNUNET_OK != TALER_TESTING_get_trait_tip_id
+ (authorize_cmd, 0, &tip_id))
+ TALER_TESTING_FAIL (is);
+
+ tps->tpo = TALER_MERCHANT_tip_pickup (tps->ctx,
+ tps->merchant_url,
+ tip_id,
+ num_planchets,
+ planchets,
+ &pickup_cb,
+ tps);
+ GNUNET_assert (NULL != tps->tpo);
+ }
+}
+
+
+static void
+tip_pickup_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct TipPickupState *tps = cls;
+
+ if (NULL != tps->tpo)
+ {
+ TALER_LOG_WARNING ("Tip-pickup operation"
+ " did not complete\n");
+ TALER_MERCHANT_tip_pickup_cancel (tps->tpo);
+ }
+
+ GNUNET_free (tps);
+}
+
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ * to return in case there were multiple generated
+ * by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+tip_pickup_traits (void *cls,
+ void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct TipPickupState *tps = cls;
+ struct TALER_TESTING_Trait traits[tps->num_coins + 1];
+
+ for (unsigned int i=0; i<tps->num_coins; i++)
+ traits[i] = TALER_TESTING_make_trait_planchet_secrets
+ (0, &tps->psa[i]);
+ traits[tps->num_coins + 1] = TALER_TESTING_trait_end ();
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ return GNUNET_SYSERR;
+}
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_pickup
+ (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *authorize_reference,
+ const char **amounts,
+ struct TALER_EXCHANGE_Handle *exchange)
+{
+ struct TipPickupState *tps;
+ struct TALER_TESTING_Command cmd;
+
+ tps = GNUNET_new (struct TipPickupState);
+ tps->merchant_url = merchant_url;
+ tps->ctx = ctx;
+ tps->authorize_reference = authorize_reference;
+ tps->amounts = amounts;
+ tps->exchange = exchange;
+
+ cmd.cls = tps;
+ cmd.label = label;
+ cmd.run = &tip_pickup_run;
+ cmd.cleanup = &tip_pickup_cleanup;
+ cmd.traits = &tip_pickup_traits;
+
+ return cmd;
+}
+
+
+/* end of testing_api_cmd_tip.c */
diff --git a/src/lib/testing_api_cmd_track.c b/src/lib/testing_api_cmd_track.c
new file mode 100644
index 00000000..e6653738
--- /dev/null
+++ b/src/lib/testing_api_cmd_track.c
@@ -0,0 +1,362 @@
+/*
+ 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.c
+ * @brief command to test /track/transaction and /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"
+
+struct TrackTransactionState
+{
+ struct TALER_MERCHANT_TrackTransactionHandle *tth;
+
+ struct TALER_TESTING_Interpreter *is;
+
+ const char *merchant_url;
+
+ struct GNUNET_CURL_Context *ctx;
+
+ unsigned int http_status;
+
+ const char *transfer_reference;
+
+ const char *pay_reference;
+
+ const char *wire_fee;
+};
+
+struct TrackTransferState
+{
+
+ struct TALER_MERCHANT_TrackTransferHandle *tth;
+
+ struct TALER_TESTING_Interpreter *is;
+
+ const char *merchant_url;
+
+ struct GNUNET_CURL_Context *ctx;
+
+ unsigned int http_status;
+
+ const char *check_bank_reference;
+
+ /**
+ * #OC_PAY command which we expect in the result.
+ * Since we are tracking a bank transaction, we want to know
+ * which (Taler) deposit is associated with the bank
+ * transaction being tracked now.
+ */
+ const char *pay_reference;
+};
+
+/**
+ * Function called with detailed wire transfer data.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code we got,
+ * 0 on exchange protocol violation
+ * @param ec taler-specific error code
+ * @param json original json reply
+ */
+static void
+track_transaction_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const json_t *json)
+{
+ struct TrackTransactionState *tts = cls;
+
+ tts->tth = NULL;
+ if (tts->http_status != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ http_status,
+ ec,
+ TALER_TESTING_interpreter_get_current_label
+ (tts->is));
+ TALER_TESTING_interpreter_fail (tts->is);
+ return;
+ }
+ if (MHD_HTTP_OK != http_status)
+ TALER_TESTING_FAIL (tts->is);
+
+ TALER_TESTING_interpreter_next (tts->is);
+}
+
+/**
+ * Callback for a /track/transfer operation
+ *
+ * @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,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const struct TALER_ExchangePublicKeyP *sign_key,
+ const json_t *json,
+ 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 != http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ http_status,
+ ec,
+ TALER_TESTING_interpreter_get_current_label
+ (tts->is));
+ TALER_TESTING_interpreter_fail (tts->is);
+ return;
+ }
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ TALER_TESTING_interpreter_next (tts->is);
+}
+
+
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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;
+ 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, 0, &exchange_url))
+ TALER_TESTING_FAIL (is);
+ tts->tth = TALER_MERCHANT_track_transfer (tts->ctx,
+ tts->merchant_url,
+ "default",
+ "test",
+ wtid,
+ exchange_url,
+ &track_transfer_cb,
+ tts);
+ GNUNET_assert (NULL != tts->tth);
+}
+
+/**
+ * Runs the command. Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks. Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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
+ (tts->ctx,
+ tts->merchant_url,
+ "default",
+ order_id,
+ &track_transaction_cb,
+ tts);
+
+ GNUNET_assert (NULL != tts->tth);
+}
+
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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);
+}
+
+/**
+ * Clean up after the command. Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+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);
+ }
+ GNUNET_free (tts);
+}
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transaction
+ (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *transfer_reference,
+ const char *pay_reference,
+ const char *wire_fee)
+{
+ struct TrackTransactionState *tts;
+ struct TALER_TESTING_Command cmd;
+
+ tts = GNUNET_new (struct TrackTransactionState);
+ tts->merchant_url = merchant_url;
+ tts->ctx = ctx;
+ tts->http_status = http_status;
+ tts->transfer_reference = transfer_reference;
+ tts->pay_reference = pay_reference;
+ tts->wire_fee = wire_fee;
+
+ cmd.cls = tts;
+ cmd.label = label;
+ cmd.run = &track_transaction_run;
+ cmd.cleanup = &track_transaction_cleanup;
+ // traits?
+
+ return cmd;
+}
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transfer
+ (const char *label,
+ const char *merchant_url,
+ struct GNUNET_CURL_Context *ctx,
+ unsigned int http_status,
+ const char *check_bank_reference,
+ const char *pay_reference)
+{
+ struct TrackTransferState *tts;
+ struct TALER_TESTING_Command cmd;
+
+ tts = GNUNET_new (struct TrackTransferState);
+ tts->merchant_url = merchant_url;
+ tts->ctx = ctx;
+ tts->http_status = http_status;
+ tts->check_bank_reference = check_bank_reference;
+ tts->pay_reference = pay_reference;
+
+ cmd.cls = tts;
+ cmd.label = label;
+ cmd.run = &track_transfer_run;
+ cmd.cleanup = &track_transfer_cleanup;
+ // traits?
+
+ return cmd;
+}
+
+/* end of testing_api_cmd_track.c */
diff --git a/src/lib/testing_api_helpers.c b/src/lib/testing_api_helpers.c
new file mode 100644
index 00000000..e4458569
--- /dev/null
+++ b/src/lib/testing_api_helpers.c
@@ -0,0 +1,185 @@
+/*
+ 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)
+{
+ struct GNUNET_OS_Process *merchant_proc;
+ unsigned int iter;
+
+ merchant_proc
+ = GNUNET_OS_start_process (GNUNET_NO,
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-merchant-httpd",
+ "taler-merchant-httpd",
+ "-c", config_filename,
+ NULL);
+ if (NULL == merchant_proc)
+ MERCHANT_FAIL ();
+
+ /* give child time to start and bind against the socket */
+ fprintf (stderr,
+ "Waiting for `taler-merchant-httpd' to be ready");
+ 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, ".");
+ sleep (1);
+ iter++;
+ }
+ while (0 != system (
+ "wget -q -t 1 -T 1 http://127.0.0.1:8082/" \
+ " -o /dev/null -O /dev/null"));
+ 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", "test_merchant_api.conf",
+ "-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/lib/testing_api_trait_hash.c b/src/lib/testing_api_trait_hash.c
new file mode 100644
index 00000000..d2d99d82
--- /dev/null
+++ b/src/lib/testing_api_trait_hash.c
@@ -0,0 +1,117 @@
+/*
+ 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>
+
+#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 trait from
+ * @param index which signature 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,
+ (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 planchet_secrets set to the offered secrets.
+ * @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 trait from
+ * @param index which hash code to pick if @a cmd has multiple
+ * on offer
+ * @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,
+ (void **) h_contract_terms,
+ TALER_TESTING_TRAIT_H_CONTRACT_TERMS,
+ index);
+}
+
+
+/**
+ * Offer contract terms hash code.
+ *
+ * @param index which hash code to offer if there are
+ * multiple on offer
+ * @param h_contract_terms set to the offered hash code.
+ * @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/lib/testing_api_trait_merchant_sig.c b/src/lib/testing_api_trait_merchant_sig.c
new file mode 100644
index 00000000..26bedde8
--- /dev/null
+++ b/src/lib/testing_api_trait_merchant_sig.c
@@ -0,0 +1,73 @@
+/*
+ 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,
+ (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/lib/testing_api_trait_planchet.c b/src/lib/testing_api_trait_planchet.c
new file mode 100644
index 00000000..8b225c3a
--- /dev/null
+++ b/src/lib/testing_api_trait_planchet.c
@@ -0,0 +1,73 @@
+/*
+ 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 which signature to pick if @a cmd has multiple
+ * on offer
+ * @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,
+ (void **) planchet_secrets,
+ TALER_TESTING_TRAIT_PLANCHET_SECRETS,
+ index);
+}
+
+/**
+ * Offer planchet secrets.
+ *
+ * @param index which secrets to offer if there are multiple
+ * on offer
+ * @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/lib/testing_api_trait_refund_entry.c b/src/lib/testing_api_trait_refund_entry.c
new file mode 100644
index 00000000..c5728b92
--- /dev/null
+++ b/src/lib/testing_api_trait_refund_entry.c
@@ -0,0 +1,77 @@
+/*
+ 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 trait from
+ * @param index which signature to pick if @a cmd has multiple
+ * on offer
+ * @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,
+ (void **) refund_entry,
+ TALER_TESTING_TRAIT_REFUND_ENTRY,
+ index);
+}
+
+/**
+ * Offer refund entry.
+ *
+ * @param index which tip id to offer if there are
+ * multiple on 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/lib/testing_api_trait_string.c b/src/lib/testing_api_trait_string.c
new file mode 100644
index 00000000..74b1ca04
--- /dev/null
+++ b/src/lib/testing_api_trait_string.c
@@ -0,0 +1,131 @@
+/*
+ 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 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,
+ (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 set to the offered reference.
+ * @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,
+ (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 */