diff options
Diffstat (limited to 'src/testing/testing_api_cmd_post_transfers.c')
-rw-r--r-- | src/testing/testing_api_cmd_post_transfers.c | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/src/testing/testing_api_cmd_post_transfers.c b/src/testing/testing_api_cmd_post_transfers.c new file mode 100644 index 00000000..f5087c2d --- /dev/null +++ b/src/testing/testing_api_cmd_post_transfers.c @@ -0,0 +1,473 @@ +/* + 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_post_transfers.c + * @brief command to test POST /transfers + * @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 of a "POST /transfers" CMD. + */ +struct PostTransfersState +{ + + /** + * Handle for a "POST /transfers" request. + */ + struct TALER_MERCHANT_PostTransfersHandle *pth; + + /** + * Handle for a "GET" bank account history request. + */ + struct TALER_BANK_DebitHistoryHandle *dhh; + + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Base URL of the merchant serving the request. + */ + const char *merchant_url; + + /** + * URL of the bank to run history on. + */ + const char *bank_url; + + /** + * URL of the bank to run history on (set once @e found is set). + */ + char *exchange_url; + + /** + * Payto URI to filter on. + */ + const char *payto_uri; + + /** + * Authentication details to authenticate to the bank. + */ + struct TALER_BANK_AuthenticationData auth; + + /** + * Set once we discovered the WTID and thus @e found is true. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * the credit amount to look for at @e bank_url. + */ + struct TALER_Amount credit_amount; + + /** + * Expected HTTP response code. + */ + unsigned int http_status; + + /** + * Array of deposit command labels we expect to see aggregated. + */ + const char **deposits; + + /** + * Length of @e deposits. + */ + unsigned int deposits_length; + + /** + * Set to true once @e wtid and @e exchange_url are initialized. + */ + bool found; +}; + + +/** + * Callback for a POST /transfers operation. + * + * @param cls closure for this function + * @param hr HTTP response details + * @param execution_time when did the transfer happen (according to the exchange), + * #GNUNET_TIME_UNIT_FOREVER_ABS if the transfer did not yet happen or if + * we have no data from the exchange about it + * @param total_amount total amount of the wire transfer, or NULL if the exchange did + * not provide any details + * @param wire_fee how much did the exchange charge in terms of wire fees, or NULL + * if the exchange did not provide any details + * @param details_length length of the @a details array + * @param details array with details about the combined transactions + */ +static void +transfers_cb (void *cls, + const struct TALER_MERCHANT_HttpResponse *hr, + struct GNUNET_TIME_Absolute execution_time, + const struct TALER_Amount *total_amount, + const struct TALER_Amount *wire_fee, + unsigned int details_length, + const struct TALER_MERCHANT_TrackTransferDetail details[]) +{ + struct PostTransfersState *pts = cls; + + pts->pth = NULL; + if (pts->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 (pts->is)); + TALER_TESTING_interpreter_fail (pts->is); + return; + } + switch (hr->http_status) + { + case MHD_HTTP_OK: + { + struct TALER_Amount total; + + if (0 > + TALER_amount_subtract (&total, + total_amount, + wire_fee)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return; + } + if (0 != + TALER_amount_cmp (&total, + &pts->credit_amount)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return; + } + TALER_amount_get_zero (total.currency, + &total); + for (unsigned int i = 0; i<details_length; i++) + { + const struct TALER_MERCHANT_TrackTransferDetail *tdd = &details[i]; + struct TALER_Amount sum; + struct TALER_Amount fees; + + TALER_amount_get_zero (tdd->deposit_value.currency, + &sum); + TALER_amount_get_zero (tdd->deposit_fee.currency, + &fees); + for (unsigned int j = 0; j<pts->deposits_length; j++) + { + const char *label = pts->deposits[j]; + const struct TALER_TESTING_Command *cmd; + const json_t *contract_terms; + const struct TALER_Amount *deposit_value; + const char *order_id; + + cmd = TALER_TESTING_interpreter_lookup_command (pts->is, + label); + if (NULL == cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return; + } + if ( (GNUNET_OK != + TALER_TESTING_get_trait_contract_terms (cmd, + 0, + &contract_terms)) || + (GNUNET_OK != + TALER_TESTING_get_trait_amount_obj (cmd, + 0, + &deposit_value)) ) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return; + } + order_id = json_string_value (json_object_get (contract_terms, + "order_id")); + if (NULL == order_id) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return; + } + if (0 != strcmp (tdd->order_id, + order_id)) + continue; + if (0 > + TALER_amount_add (&sum, + &sum, + deposit_value)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return; + } + // FIXME #6236: also would want to add deposit_fees, but unavailable as traits right now! + } + if (0 != + TALER_amount_cmp (&sum, + &tdd->deposit_value)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return; + } +#if FIXME6236 + if (0 != + TALER_amount_cmp (&fees, + tdd->deposit_fee)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return; + } + GNUNET_assert (0 <= + TALER_amount_add (&total, + &total, + &tdd->deposit_value)); + GNUNET_assert (0 <= + TALER_amount_subtract (&total, + &total, + &tdd->deposit_fee)); +#endif + } +#if FIXME6236 + if (0 != + TALER_amount_cmp (&total, + pts->credit_amount)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return; + } +#endif + break; + } + default: + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unhandled HTTP status.\n"); + } + TALER_TESTING_interpreter_next (pts->is); +} + + +/** + * Callbacks of this type are used to serve the result of asking + * the bank for the debit transaction history. + * + * @param cls closure with a `struct PostTransfersState *` + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the bank's reply is bogus (fails to follow the protocol), + * #MHD_HTTP_NO_CONTENT if there are no more results; on success the + * last callback is always of this status (even if `abs(num_results)` were + * already returned). + * @param ec detailed error code + * @param serial_id monotonically increasing counter corresponding to the transaction + * @param details details about the wire transfer + * @param json detailed response from the HTTPD, or NULL if reply was not in JSON + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration + */ +static int +debit_cb ( + void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint64_t serial_id, + const struct TALER_BANK_DebitDetails *details, + const json_t *json) +{ + struct PostTransfersState *pts = cls; + + if (MHD_HTTP_NO_CONTENT == http_status) + { + pts->dhh = NULL; + if (! pts->found) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + return GNUNET_OK; + } + GNUNET_assert (NULL != pts->exchange_url); + pts->pth = TALER_MERCHANT_transfers_post (pts->is->ctx, + pts->merchant_url, + &pts->credit_amount, + &pts->wtid, + pts->payto_uri, + pts->exchange_url, + &transfers_cb, + pts); + return GNUNET_OK; + } + if (MHD_HTTP_OK != http_status) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (pts->is); + pts->dhh = NULL; + return GNUNET_SYSERR; + } + if (pts->found) + return GNUNET_OK; + if (0 != TALER_amount_cmp (&pts->credit_amount, + &details->amount)) + return GNUNET_OK; + if ( (NULL != pts->payto_uri) && + (0 != strcasecmp (pts->payto_uri, + details->credit_account_url)) ) + return GNUNET_OK; + pts->found = true; + pts->wtid = details->wtid; + pts->exchange_url = GNUNET_strdup (details->exchange_base_url); + return GNUNET_OK; +} + + +/** + * Run the "POST /transfers" CMD. First, get the bank history to find + * the wtid. + * + * @param cls closure. + * @param cmd command being run now. + * @param is interpreter state. + */ +static void +post_transfers_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct PostTransfersState *pts = cls; + + pts->is = is; + pts->dhh = TALER_BANK_debit_history (is->ctx, + NULL, + UINT64_MAX, + -INT64_MAX, + &debit_cb, + pts); + GNUNET_assert (NULL != pts->dhh); +} + + +/** + * Free the state of a "POST product" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd command being run. + */ +static void +post_transfers_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct PostTransfersState *pts = cls; + + if (NULL != pts->pth) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "POST /transfers operation did not complete\n"); + TALER_MERCHANT_transfers_post_cancel (pts->pth); + } + if (NULL != pts->dhh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "GET debit history operation did not complete\n"); + TALER_BANK_debit_history_cancel (pts->dhh); + } + GNUNET_array_grow (pts->deposits, + pts->deposits_length, + 0); + GNUNET_free_non_null (pts->exchange_url); + GNUNET_free (pts); +} + + +/** + * Define a POST /transfers CMD. Details like the WTID and + * other required parameters will be extracted from the bank + * history, using the latest transfer of the specified + * @a credit_amount to the @a merchant_url. + * + * @param label command label. + * @param merchant_url base URL of the backend serving the + * "refund increase" request. + * @param auth credentials to access the exchange's bank account + * @param bank_url URL of the exchange's bank account + * @param credit_amount amount credited + * @param http_code expected HTTP response code + * @param ... NULL-terminated list of labels (const char *) of + * deposit (commands) we expect to be aggregated in the transfer + * (assuming @a http_code is #MHD_HTTP_OK) + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_merchant_post_transfer ( + const char *label, + const struct TALER_BANK_AuthenticationData *auth, + const char *bank_url, + const char *merchant_url, + const char *credit_amount, + unsigned int http_code, + ...) +{ + struct PostTransfersState *pts; + + pts = GNUNET_new (struct PostTransfersState); + pts->bank_url = bank_url; + pts->merchant_url = merchant_url; + pts->auth = *auth; + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (credit_amount, + &pts->credit_amount)); + pts->http_status = http_code; + { + const char *clabel; + va_list ap; + + va_start (ap, http_code); + while (NULL != (clabel = va_arg (ap, const char *))) + { + GNUNET_array_append (pts->deposits, + pts->deposits_length, + clabel); + } + va_end (ap); + } + { + struct TALER_TESTING_Command cmd = { + .cls = pts, + .label = label, + .run = &post_transfers_run, + .cleanup = &post_transfers_cleanup + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_post_transfers.c */ |