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