merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit dcbac9e63f37f9a5dd8b47a2cfc068d73068d4fc
parent 6ca84e4121cf4bd1b8c66d2ef4bd58eeb7003ea3
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 26 Apr 2020 12:04:24 +0200

fix fTBFS

Diffstat:
Msrc/include/taler_merchant_service.h | 21++++++++++++---------
Msrc/include/taler_merchant_testing_lib.h | 24+++++++++++++-----------
Msrc/lib/Makefile.am | 2+-
Asrc/lib/merchant_api_post_orders.c | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/merchant_api_proposal.c | 244-------------------------------------------------------------------------------
Msrc/merchant-tools/taler-merchant-benchmark.c | 20++++++++++----------
Msrc/testing/Makefile.am | 2+-
Msrc/testing/test_merchant_api.c | 75++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/testing/test_merchant_api_twisted.c | 158++++++++++++++++++++++++++++++++++++++++----------------------------------------
Asrc/testing/testing_api_cmd_post_orders.c | 397+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/testing/testing_api_cmd_proposal.c | 401-------------------------------------------------------------------------------
Msrc/testing/testing_api_cmd_proposal_lookup.c | 2+-
12 files changed, 797 insertions(+), 794 deletions(-)

diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h @@ -1104,7 +1104,7 @@ TALER_MERCHANT_product_delete_cancel ( /** * Handle to a POST /orders operation */ -struct TALER_MERCHANT_PostOrderOperation; +struct TALER_MERCHANT_PostOrdersOperation; /** * Callbacks of this type are used to serve the result of submitting a @@ -1115,7 +1115,7 @@ struct TALER_MERCHANT_PostOrderOperation; * @param order_id order id of the newly created order */ typedef void -(*TALER_MERCHANT_PostOrderCallback) ( +(*TALER_MERCHANT_PostOrdersCallback) ( void *cls, const struct TALER_MERCHANT_HttpResponse *hr, const char *order_id); @@ -1132,12 +1132,14 @@ typedef void * @param cb_cls closure for @a cb * @return a handle for this request, NULL on error */ -struct TALER_MERCHANT_PostOrderOperation * -TALER_MERCHANT_order_post (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const json_t *order, - TALER_MERCHANT_PostOrderCallback cb, - void *cb_cls); +struct TALER_MERCHANT_PostOrdersOperation * +TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const json_t *order, + TALER_MERCHANT_PostOrdersCallback cb, + void *cb_cls); + +// TODO: implement orders_post2 with the OPTIONAL arguments! /** @@ -1147,7 +1149,8 @@ TALER_MERCHANT_order_post (struct GNUNET_CURL_Context *ctx, * @param po the proposal operation request handle */ void -TALER_MERCHANT_order_post_cancel (struct TALER_MERCHANT_PostOrderOperation *po); +TALER_MERCHANT_orders_post_cancel ( + struct TALER_MERCHANT_PostOrdersOperation *po); /** diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h @@ -407,8 +407,6 @@ TALER_TESTING_cmd_merchant_delete_product (const char *label, unsigned int http_status); -/* ******************** OLD ******************* */ - /** * Make the "proposal" command. * @@ -421,10 +419,14 @@ TALER_TESTING_cmd_merchant_delete_product (const char *label, * @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); +TALER_TESTING_cmd_merchant_post_orders (const char *label, + const char *merchant_url, + unsigned int http_status, + const char *order); + + +/* ******************** OLD ******************* */ + /** * Make a "proposal lookup" command. @@ -439,11 +441,11 @@ TALER_TESTING_cmd_proposal (const char *label, * @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); +TALER_TESTING_cmd_merchant_post_orders_lookup (const char *label, + const char *merchant_url, + unsigned int http_status, + const char *proposal_reference, + const char *order_id); /** * Make a "check payment" test command. diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -27,9 +27,9 @@ libtalermerchant_la_SOURCES = \ merchant_api_patch_product.c \ merchant_api_post_instances.c \ merchant_api_post_products.c \ + merchant_api_post_orders.c \ merchant_api_check_payment.c \ merchant_api_history.c \ - merchant_api_proposal.c \ merchant_api_proposal_lookup.c \ merchant_api_pay.c \ merchant_api_poll_payment.c \ diff --git a/src/lib/merchant_api_post_orders.c b/src/lib/merchant_api_post_orders.c @@ -0,0 +1,245 @@ +/* + 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 Lesser General Public License as + published by the Free Software Foundation; either version 2.1, + 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with TALER; see the file COPYING.LGPL. If not, + see <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/merchant_api_post_orders.c + * @brief Implementation of the POST /orders + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> +#include <taler/taler_curl_lib.h> + + +/** + * @brief A POST /orders Handle + */ +struct TALER_MERCHANT_PostOrdersOperation +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_PostOrdersCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /orders request. + * + * @param cls the `struct TALER_MERCHANT_PostOrdersOperation` + * @param response_code HTTP response code, 0 on error + * @param json response body, NULL if not JSON + */ +static void +handle_post_order_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_PostOrdersOperation *po = cls; + const char *order_id = NULL; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("order_id", + &order_id), + GNUNET_JSON_spec_end () + }; + + po->job = NULL; + switch (response_code) + { + case 0: + hr.ec = TALER_EC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + hr.http_status = 0; + hr.ec = TALER_EC_PROPOSAL_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us or + the merchant is buggy (or API version conflict); + just pass JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, merchant says one + of the signatures is invalid; as we checked them, + this should never happen, we should pass the JSON + reply to the application */ + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + po->cb (po->cb_cls, + &hr, + order_id); + if (MHD_HTTP_OK == response_code) + GNUNET_JSON_parse_free (spec); + TALER_MERCHANT_orders_post_cancel (po); +} + + +/** + * POST an order to the backend and receives the related proposal. + * + * @param ctx execution context + * @param backend_url URL of the backend + * @param order basic information about this purchase, + * to be extended by the backend + * @param cb the callback to call when a reply + * for this request is available + * @param cb_cls closure for @a proposal_cb + * @return a handle for this request, NULL on error + */ +struct TALER_MERCHANT_PostOrdersOperation * +TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const json_t *order, + TALER_MERCHANT_PostOrdersCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_PostOrdersOperation *po; + json_t *req; + CURL *eh; + + po = GNUNET_new (struct TALER_MERCHANT_PostOrdersOperation); + po->ctx = ctx; + po->cb = cb; + po->cb_cls = cb_cls; + po->url = TALER_url_join (backend_url, "orders", NULL); + req = json_pack ("{s:O}", + "order", (json_t *) order); + eh = curl_easy_init (); + if (GNUNET_OK != TALER_curl_easy_post (&po->post_ctx, + eh, + req)) + { + GNUNET_break (0); + json_decref (req); + GNUNET_free (po); + return NULL; + } + json_decref (req); + + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + po->url)); + po->job = GNUNET_CURL_job_add2 (ctx, + eh, + po->post_ctx.headers, + &handle_post_order_finished, + po); + return po; +} + + +/** + * Cancel a POST /proposal request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param po the proposal operation request handle + */ +void +TALER_MERCHANT_orders_post_cancel ( + struct TALER_MERCHANT_PostOrdersOperation *po) +{ + if (NULL != po->job) + { + GNUNET_CURL_job_cancel (po->job); + po->job = NULL; + } + GNUNET_free (po->url); + TALER_curl_easy_post_finished (&po->post_ctx); + GNUNET_free (po); +} + + +/* end of merchant_api_post_orders.c */ diff --git a/src/lib/merchant_api_proposal.c b/src/lib/merchant_api_proposal.c @@ -1,244 +0,0 @@ -/* - 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 Lesser General Public License as - published by the Free Software Foundation; either version 2.1, - 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with TALER; see the file COPYING.LGPL. If not, - see <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/merchant_api_proposal.c - * @brief Implementation of the /proposal POST - * @author Christian Grothoff - * @author Marcello Stanisci - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> -#include <taler/taler_curl_lib.h> - - -/** - * @brief A Contract Operation Handle - */ -struct TALER_MERCHANT_ProposalOperation -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_ProposalCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP POST /proposal request. - * - * @param cls the `struct TALER_MERCHANT_ProposalOperation` - * @param response_code HTTP response code, 0 on error - * @param json response body, NULL if not JSON - */ -static void -handle_proposal_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_ProposalOperation *po = cls; - const char *order_id = NULL; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("order_id", - &order_id), - GNUNET_JSON_spec_end () - }; - - po->job = NULL; - switch (response_code) - { - case 0: - hr.ec = TALER_EC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_PROPOSAL_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* This should never happen, either us or - the merchant is buggy (or API version conflict); - just pass JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, merchant says one - of the signatures is invalid; as we checked them, - this should never happen, we should pass the JSON - reply to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, - but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - GNUNET_break_op (0); - break; - } - po->cb (po->cb_cls, - &hr, - order_id); - if (MHD_HTTP_OK == response_code) - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_proposal_cancel (po); -} - - -/** - * POST an order to the backend and receives the related proposal. - * - * @param ctx execution context - * @param backend_url URL of the backend - * @param order basic information about this purchase, - * to be extended by the backend - * @param proposal_cb the callback to call when a reply - * for this request is available - * @param proposal_cb_cls closure for @a proposal_cb - * @return a handle for this request, NULL on error - */ -struct TALER_MERCHANT_ProposalOperation * -TALER_MERCHANT_order_put (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const json_t *order, - TALER_MERCHANT_ProposalCallback proposal_cb, - void *proposal_cb_cls) -{ - struct TALER_MERCHANT_ProposalOperation *po; - json_t *req; - CURL *eh; - - po = GNUNET_new (struct TALER_MERCHANT_ProposalOperation); - po->ctx = ctx; - po->cb = proposal_cb; - po->cb_cls = proposal_cb_cls; - po->url = TALER_url_join (backend_url, "order", NULL); - req = json_pack ("{s:O}", - "order", (json_t *) order); - eh = curl_easy_init (); - if (GNUNET_OK != TALER_curl_easy_post (&po->post_ctx, - eh, - req)) - { - GNUNET_break (0); - json_decref (req); - GNUNET_free (po); - return NULL; - } - json_decref (req); - - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_URL, - po->url)); - po->job = GNUNET_CURL_job_add2 (ctx, - eh, - po->post_ctx.headers, - &handle_proposal_finished, - po); - return po; -} - - -/** - * Cancel a POST /proposal request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param po the proposal operation request handle - */ -void -TALER_MERCHANT_proposal_cancel (struct TALER_MERCHANT_ProposalOperation *po) -{ - if (NULL != po->job) - { - GNUNET_CURL_job_cancel (po->job); - po->job = NULL; - } - GNUNET_free (po->url); - TALER_curl_easy_post_finished (&po->post_ctx); - GNUNET_free (po); -} - - -/* end of merchant_api_proposal.c */ diff --git a/src/merchant-tools/taler-merchant-benchmark.c b/src/merchant-tools/taler-merchant-benchmark.c @@ -344,10 +344,10 @@ run (void *cls, "create-reserve-1", CURRENCY_5, MHD_HTTP_OK), - TALER_TESTING_cmd_proposal ("create-proposal-1", - merchant_url, - MHD_HTTP_OK, - order_worth_5), + TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1", + merchant_url, + MHD_HTTP_OK, + order_worth_5), TALER_TESTING_cmd_pay ("deposit-simple", merchant_url, MHD_HTTP_OK, @@ -362,10 +362,10 @@ run (void *cls, /* Next proposal-pay cycle will be used by /track CMDs * and so it will not have to be looped over, only /track * CMDs will have to. */ - TALER_TESTING_cmd_proposal ("create-proposal-2", - merchant_url, - MHD_HTTP_OK, - order_worth_5_track), + TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2", + merchant_url, + MHD_HTTP_OK, + order_worth_5_track), TALER_TESTING_cmd_pay ("deposit-simple-2", merchant_url, MHD_HTTP_OK, @@ -410,7 +410,7 @@ run (void *cls, CURRENCY_5, MHD_HTTP_OK), - TALER_TESTING_cmd_proposal + TALER_TESTING_cmd_merchant_post_orders ("create-unaggregated-proposal", alt_instance_url, MHD_HTTP_OK, @@ -453,7 +453,7 @@ run (void *cls, CURRENCY_5, MHD_HTTP_OK), - TALER_TESTING_cmd_proposal + TALER_TESTING_cmd_merchant_post_orders ("create-twocoins-proposal", merchant_url, MHD_HTTP_OK, diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am @@ -24,6 +24,7 @@ libtalermerchanttesting_la_SOURCES = \ testing_api_cmd_lock_product.c \ testing_api_cmd_post_instances.c \ testing_api_cmd_post_products.c \ + testing_api_cmd_post_orders.c \ testing_api_cmd_patch_instance.c \ testing_api_cmd_patch_product.c \ \ @@ -33,7 +34,6 @@ libtalermerchanttesting_la_SOURCES = \ 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 \ diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c @@ -261,10 +261,10 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\" },\ @@ -336,10 +336,10 @@ run (void *cls, }; struct TALER_TESTING_Command double_spending[] = { - TALER_TESTING_cmd_proposal ("create-proposal-2", - merchant_url, - MHD_HTTP_OK, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\" },\ @@ -348,11 +348,11 @@ run (void *cls, \"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_merchant_post_orders_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, @@ -481,10 +481,10 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\" },\ @@ -533,10 +533,10 @@ run (void *cls, 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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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},\ @@ -581,10 +581,11 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ( + "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\"},\ @@ -745,10 +746,10 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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},\ @@ -796,10 +797,10 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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},\ @@ -853,10 +854,10 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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},\ diff --git a/src/testing/test_merchant_api_twisted.c b/src/testing/test_merchant_api_twisted.c @@ -216,10 +216,10 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\"},\ @@ -253,7 +253,7 @@ run (void *cls, /**** Covering /check-payment ****/ struct TALER_TESTING_Command check_payment[] = { - TALER_TESTING_cmd_proposal + TALER_TESTING_cmd_merchant_post_orders ("proposal-for-check-payment", twister_merchant_url, MHD_HTTP_OK, @@ -305,23 +305,23 @@ run (void *cls, */ 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_merchant_post_orders ("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\"}"), + TALER_TESTING_cmd_merchant_post_orders ("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 @@ -331,10 +331,10 @@ run (void *cls, 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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\"},\ @@ -349,10 +349,10 @@ run (void *cls, 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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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},\ @@ -365,31 +365,31 @@ run (void *cls, * 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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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"), + TALER_TESTING_cmd_merchant_post_orders_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"), + TALER_TESTING_cmd_merchant_post_orders_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 @@ -398,10 +398,10 @@ run (void *cls, /* 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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\"},\ @@ -417,12 +417,12 @@ run (void *cls, "contract_terms"), /* lookup! */ - TALER_TESTING_cmd_proposal_lookup ("lookup-5", - twister_merchant_url, - // expected response code. - 0, - "create-proposal-5", - NULL), + TALER_TESTING_cmd_merchant_post_orders_lookup ("lookup-5", + twister_merchant_url, + // expected response code. + 0, + "create-proposal-5", + NULL), TALER_TESTING_cmd_end () }; @@ -486,14 +486,14 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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},\ @@ -535,10 +535,10 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\"},\ @@ -610,10 +610,10 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\"},\ @@ -707,10 +707,10 @@ run (void *cls, "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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\"},\ @@ -769,10 +769,10 @@ run (void *cls, 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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\"},\ @@ -780,10 +780,10 @@ run (void *cls, \"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\",\ + TALER_TESTING_cmd_merchant_post_orders ("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\"},\ diff --git a/src/testing/testing_api_cmd_post_orders.c b/src/testing/testing_api_cmd_post_orders.c @@ -0,0 +1,397 @@ +/* + 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 testing_api_cmd_post_orders.c + * @brief command to run POST /orders + * @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 "POST /orders" CMD. + */ +struct OrdersState +{ + + /** + * 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 /orders operation handle. + */ + struct TALER_MERCHANT_PostOrdersOperation *po; + + /** + * The (initial) GET /orders/$ID operation handle. + * The logic is such that after a orders creation, + * it soon makes a orders 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 orders. + */ + 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 +orders_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct OrdersState *ps = cls; + // FIXME: wtf is this? +#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 "orders" CMD state with backend-provided + * values. Also double-checks that the orders 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 +orders_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 OrdersState *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 + * POST /orders. NOTE: no contract terms are included + * here; they need to be taken via the "orders lookup" + * method. + * + * @param cls closure. + * @param hr HTTP response + * @param order_id order id of the orders. + */ +static void +order_cb (void *cls, + const struct TALER_MERCHANT_HttpResponse *hr, + const char *order_id) +{ + struct OrdersState *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 ("/orders, 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 /orders: %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, + &orders_lookup_initial_cb, + ps))) + TALER_TESTING_FAIL (ps->is); +} + + +/** + * Run a "orders" CMD. + * + * @param cls closure. + * @param cmd command currently being run. + * @param is interpreter state. + */ +static void +orders_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct OrdersState *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_orders_post (is->ctx, + ps->merchant_url, + order, + &order_cb, + ps); + json_decref (order); + GNUNET_assert (NULL != ps->po); +} + + +/** + * Free the state of a "orders" CMD, and possibly + * cancel it if it did not complete. + * + * @param cls closure. + * @param cmd command being freed. + */ +static void +orders_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct OrdersState *ps = cls; + + if (NULL != ps->po) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command '%s' did not complete (orders put)\n", + cmd->label); + TALER_MERCHANT_orders_post_cancel (ps->po); + ps->po = NULL; + } + + if (NULL != ps->plo) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command '%s' did not complete" + " (orders 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 "orders" command. + * + * @param label command label + * @param merchant_url base URL of the merchant serving + * the orders 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_merchant_post_orders (const char *label, + const char *merchant_url, + unsigned int http_status, + const char *order) +{ + struct OrdersState *ps; + + ps = GNUNET_new (struct OrdersState); + ps->order = order; + ps->http_status = http_status; + ps->merchant_url = merchant_url; + { + struct TALER_TESTING_Command cmd = { + .cls = ps, + .label = label, + .run = &orders_run, + .cleanup = &orders_cleanup, + .traits = &orders_traits + }; + + return cmd; + } +} diff --git a/src/testing/testing_api_cmd_proposal.c b/src/testing/testing_api_cmd_proposal.c @@ -1,401 +0,0 @@ -/* - 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 @@ -283,7 +283,7 @@ proposal_lookup_traits (void *cls, * @return the command. */ struct TALER_TESTING_Command -TALER_TESTING_cmd_proposal_lookup +TALER_TESTING_cmd_merchant_post_orders_lookup (const char *label, const char *merchant_url, unsigned int http_status,