diff options
author | Marcello Stanisci <stanisci.m@gmail.com> | 2018-02-12 17:34:59 +0100 |
---|---|---|
committer | Marcello Stanisci <stanisci.m@gmail.com> | 2018-02-21 17:04:16 +0100 |
commit | 481eda431dca5a450ebcc6949753e8ba85e1f9be (patch) | |
tree | c65689f1e2c980cff77e56e0e04bc153e1242fe5 | |
parent | 7820b4b0021ba63027d140765c4167feeee40246 (diff) |
Implementing new style test instructions.
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | src/include/taler_merchant_testing_lib.h | 507 | ||||
-rw-r--r-- | src/lib/Makefile.am | 51 | ||||
-rw-r--r-- | src/lib/merchant_api_refund.c | 9 | ||||
-rw-r--r-- | src/lib/test_merchant_api.c | 4 | ||||
-rw-r--r-- | src/lib/test_merchant_api.conf | 10 | ||||
-rw-r--r-- | src/lib/test_merchant_api_new.c | 695 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_history.c | 302 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_pay.c | 1538 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_proposal.c | 560 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_refund.c | 457 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_tip.c | 777 | ||||
-rw-r--r-- | src/lib/testing_api_cmd_track.c | 362 | ||||
-rw-r--r-- | src/lib/testing_api_helpers.c | 185 | ||||
-rw-r--r-- | src/lib/testing_api_trait_hash.c | 117 | ||||
-rw-r--r-- | src/lib/testing_api_trait_merchant_sig.c | 73 | ||||
-rw-r--r-- | src/lib/testing_api_trait_planchet.c | 73 | ||||
-rw-r--r-- | src/lib/testing_api_trait_refund_entry.c | 77 | ||||
-rw-r--r-- | src/lib/testing_api_trait_string.c | 131 |
19 files changed, 5919 insertions, 10 deletions
@@ -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", + ×tamp), + 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 */ |