From e98ca0d39480b9cb23f7d0f620d9ca10d9afa7aa Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 14 Oct 2020 23:02:02 +0200 Subject: implementing long-polling test commands for wallet get order operation (#6466) --- src/backend/taler-merchant-httpd_get-orders-ID.c | 8 +- src/include/taler_merchant_service.h | 6 +- src/include/taler_merchant_testing_lib.h | 37 +- src/testing/testing_api_cmd_merchant_get_order.c | 20 +- src/testing/testing_api_cmd_wallet_get_order.c | 444 ++++++++++++++++++++++- 5 files changed, 492 insertions(+), 23 deletions(-) diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c b/src/backend/taler-merchant-httpd_get-orders-ID.c index 42e56200..82404f6a 100644 --- a/src/backend/taler-merchant-httpd_get-orders-ID.c +++ b/src/backend/taler-merchant-httpd_get-orders-ID.c @@ -114,14 +114,14 @@ struct GetOrderData struct TALER_Amount refund_amount; /** - * Did we suspend @a connection? + * Return code: #TALER_EC_NONE if successful. */ - bool suspended; + enum TALER_ErrorCode ec; /** - * Return code: #TALER_EC_NONE if successful. + * Did we suspend @a connection? */ - enum TALER_ErrorCode ec; + bool suspended; /** * Set to true if we are dealing with an unclaimed order diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index e34226a0..cd16caca 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -1466,7 +1466,7 @@ struct TALER_MERCHANT_OrderWalletGetHandle; /** - * Callback to process a GET /orders/$ID request + * Callback to process a GET /orders/$ID response * * @param cls closure * @param hr HTTP response details @@ -1509,7 +1509,7 @@ typedef void * @param timeout timeout to use in long polling (how long may the server wait to reply * before generating an unpaid response). Note that this is just provided to * the server, we as client will block until the response comes back or until - * #TALER_MERCHANT_order_get_cancel() is called. + * #TALER_MERCHANT_wallet_order_get_cancel() is called. * @param session_id for which session should the payment status be checked. Use * NULL to disregard sessions. * @param min_refund long poll for the service to approve a refund exceeding this value; @@ -1825,7 +1825,7 @@ typedef void * @param timeout timeout to use in long polling (how long may the server wait to reply * before generating an unpaid response). Note that this is just provided to * the server, we as client will block until the response comes back or until - * #TALER_MERCHANT_order_get_cancel() is called. + * #TALER_MERCHANT_merchant_order_get_cancel() is called. * @param cb callback which will work the response gotten from the backend * @param cb_cls closure to pass to @a cb * @return handle for this operation, NULL upon errors diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h index ac500766..8b53c324 100644 --- a/src/include/taler_merchant_testing_lib.h +++ b/src/include/taler_merchant_testing_lib.h @@ -575,6 +575,13 @@ TALER_TESTING_cmd_merchant_get_orders (const char *label, /** * Start a long poll for GET /private/orders. + * + * FIXME: needs additional arguments to specify range to long poll for! + * + * @param label the command label + * @param merchant_url base URL of the merchant which will + * serve the request. + * @param timeout how long to wait for the request to complete */ struct TALER_TESTING_Command TALER_TESTING_cmd_poll_orders_start (const char *label, @@ -584,6 +591,10 @@ TALER_TESTING_cmd_poll_orders_start (const char *label, /** * Complete a long poll for GET /private/orders. + * + * @param label the command label + * @param http_status expected HTTP response code + * @param poll_start_reference reference to the #TALER_TESTING_cmd_poll_orders_start command */ struct TALER_TESTING_Command TALER_TESTING_cmd_poll_orders_conclude (const char *label, @@ -614,23 +625,39 @@ TALER_TESTING_cmd_wallet_get_order (const char *label, /** - * Start a long poll for GET /private/orders/$ORDER_ID. + * Start a long poll for GET /orders/$ORDER_ID. + * + * @param label the command label + * @param merchant_url base URL of the merchant which will + * serve the request. + * @param order_ref reference to a command that created an order. + * @param timeout how long to wait for the request to complete + * @param await_refund NULL to not wait for refund, amount of value + * zero to wait for any refund amount, non-zero to poll + * for refund exceeding the given amount */ struct TALER_TESTING_Command TALER_TESTING_cmd_wallet_poll_order_start ( const char *label, const char *merchant_url, - const char *order_id, - struct GNUNET_TIME_Relative timeout); + const char *order_ref, + struct GNUNET_TIME_Relative timeout, + const struct TALER_Amount *await_refund); /** - * Complete a long poll for GET /private/orders/$ORDER_ID. + * Complete a long poll for GET /orders/$ORDER_ID. + * + * @param label the command label + * @param expected_http_status expected HTTP response code + * @param expected_refund_amount refund expected, NULL for no refund expected + * @param poll_start_reference reference to the #TALER_TESTING_cmd_wallet_poll_order_start command */ struct TALER_TESTING_Command TALER_TESTING_cmd_wallet_poll_order_conclude ( const char *label, - unsigned int http_status, + unsigned int expected_http_status, + const char *expected_refund_amount, const char *poll_start_reference); diff --git a/src/testing/testing_api_cmd_merchant_get_order.c b/src/testing/testing_api_cmd_merchant_get_order.c index 241562e0..002442ac 100644 --- a/src/testing/testing_api_cmd_merchant_get_order.c +++ b/src/testing/testing_api_cmd_merchant_get_order.c @@ -63,11 +63,6 @@ struct MerchantGetOrderState */ enum TALER_MERCHANT_OrderStatusCode osc; - /** - * Whether the order was refunded or not. - */ - bool refunded; - /** * A NULL-terminated list of refunds associated with this order. */ @@ -78,11 +73,6 @@ struct MerchantGetOrderState */ unsigned int refunds_length; - /** - * Whether the order was wired or not. - */ - bool wired; - /** * A NULL-terminated list of transfers associated with this order. */ @@ -102,6 +92,16 @@ struct MerchantGetOrderState * The length of @e forgets. */ unsigned int forgets_length; + + /** + * Whether the order was refunded or not. + */ + bool refunded; + + /** + * Whether the order was wired or not. + */ + bool wired; }; diff --git a/src/testing/testing_api_cmd_wallet_get_order.c b/src/testing/testing_api_cmd_wallet_get_order.c index d667f101..56ff4746 100644 --- a/src/testing/testing_api_cmd_wallet_get_order.c +++ b/src/testing/testing_api_cmd_wallet_get_order.c @@ -149,7 +149,7 @@ wallet_get_order_cb ( TALER_TESTING_interpreter_fail (gos->is); return; } - if (!paid_b) + if (! paid_b) { /* FIXME: Check all of the members of `pud` */ struct TALER_MERCHANT_PayUriData pud; @@ -331,4 +331,446 @@ TALER_TESTING_cmd_wallet_get_order (const char *label, } +struct WalletPollOrderConcludeState +{ + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Reference to a command that can provide a poll order start command. + */ + const char *start_reference; + + /** + * Task to wait for the deadline. + */ + struct GNUNET_SCHEDULER_Task *task; + + /** + * Amount of a refund expected. + */ + struct TALER_Amount expected_refund_amount; + + /** + * Expected HTTP response status code. + */ + unsigned int expected_http_status; + + /** + * Are we expecting a refund? + */ + bool expected_refund; +}; + + +struct WalletPollOrderStartState +{ + /** + * The merchant base URL. + */ + const char *merchant_url; + + /** + * The handle to the current GET /orders/$ORDER_ID request. + */ + struct TALER_MERCHANT_OrderWalletGetHandle *ogh; + + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Reference to a command that created an order. + */ + const char *order_ref; + + /** + * How long to wait for server to return a response. + */ + struct GNUNET_TIME_Relative timeout; + + /** + * Conclude state waiting for completion (if any). + */ + struct WalletPollOrderConcludeState *cs; + + /** + * The HTTP status code returned by the backend. + */ + unsigned int http_status; + + /** + * When the request should be completed by. + */ + struct GNUNET_TIME_Absolute deadline; + + /** + * Minimum refund to wait for. + */ + struct TALER_Amount refund_threshold; + + /** + * Available refund as returned by the merchant. + */ + struct TALER_Amount refund_available; + + /** + * Should we poll for a refund? + */ + bool wait_for_refund; + + /** + * Did we receive a refund according to response from the merchant? + */ + bool refunded; + + /** + * Was the order paid according to response from the merchant? + */ + bool paid; + + /** + * Has the order a pending refund according to response from the merchant? + */ + bool refund_pending; +}; + + +/** + * Task called when either the timeout for the GET /private/order/ command + * expired or we got a response. Checks if the result is what we expected. + * + * @param cls a `struct WalletPollOrderConcludeState` + */ +static void +conclude_task (void *cls) +{ + struct WalletPollOrderConcludeState *ppc = cls; + const struct TALER_TESTING_Command *poll_cmd; + struct WalletPollOrderStartState *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->ogh) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected poll GET /orders/$ORDER_ID 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); + } + if (ppc->expected_refund != cps->refunded) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Order was %srefunded, contrary to our expectations\n", + cps->refunded ? "" : "NOT "); + TALER_TESTING_FAIL (ppc->is); + } + if (cps->refunded) + { + if (0 != TALER_amount_cmp (&ppc->expected_refund_amount, + &cps->refund_available)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Refund amount %s does not match our expectation!\n", + TALER_amount2s (&cps->refund_available)); + TALER_TESTING_FAIL (ppc->is); + } + } + // FIXME: add checks for cps->paid/refund_available status flags? + now = GNUNET_TIME_absolute_get (); + if ((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); + } + TALER_TESTING_interpreter_next (ppc->is); +} + + +/** + * Process response from a GET /orders/$ID request + * + * @param cls a `struct WalletPollOrderStartState *` + * @param hr HTTP response details + * @param paid #GNUNET_YES if the payment is settled, #GNUNET_NO if not + * settled, #GNUNET_SYSERR on error + * (note that refunded payments are returned as paid!) + * @param refunded #GNUNET_YES if there is at least on refund on this payment, + * #GNUNET_NO if refunded, #GNUNET_SYSERR or error + * @param refund_pending #GNUNET_YES if there are refunds waiting to be + * obtained, #GNUNET_NO if all refunds have been obtained, #GNUNET_SYSERR + * on error. + * @param refunded_amount amount that was refunded, NULL if there + * was no refund + * @param taler_pay_uri the URI that instructs the wallets to process + * the payment + * @param already_paid_order_id equivalent order that this customer + * paid already, or NULL for none + */ +static void +wallet_poll_order_cb ( + void *cls, + const struct TALER_MERCHANT_HttpResponse *hr, + enum GNUNET_GenericReturnValue paid, + enum GNUNET_GenericReturnValue refunded, + enum GNUNET_GenericReturnValue refund_pending, + struct TALER_Amount *refund_amount, + const char *taler_pay_uri, + const char *already_paid_order_id) +{ + struct WalletPollOrderStartState *pos = cls; + + pos->ogh = NULL; + pos->http_status = hr->http_status; + switch (hr->http_status) + { + case MHD_HTTP_OK: + pos->paid = (GNUNET_YES == paid); + pos->refunded = (GNUNET_YES == refunded); + pos->refund_pending = (GNUNET_YES == refund_pending); + if (NULL != refund_amount) + pos->refund_available = *refund_amount; + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unhandled HTTP status.\n"); + break; + } + if (NULL != pos->cs) + { + GNUNET_SCHEDULER_cancel (pos->cs->task); + pos->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task, + pos->cs); + } +} + + +/** + * Run the "GET order" CMD. + * + * @param cls closure. + * @param cmd command being run now. + * @param is interpreter state. + */ +static void +wallet_poll_order_start_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct WalletPollOrderStartState *pos = cls; + const struct TALER_TESTING_Command *order_cmd; + const char *order_id; + const struct GNUNET_HashCode *h_contract; + + order_cmd = TALER_TESTING_interpreter_lookup_command ( + is, + pos->order_ref); + + if (GNUNET_OK != + TALER_TESTING_get_trait_order_id (order_cmd, + 0, + &order_id)) + TALER_TESTING_FAIL (is); + + if (GNUNET_OK != + TALER_TESTING_get_trait_h_contract_terms (order_cmd, + 0, + &h_contract)) + TALER_TESTING_FAIL (is); + + /* add 1s grace time to timeout */ + pos->deadline + = GNUNET_TIME_absolute_add (GNUNET_TIME_relative_to_absolute (pos->timeout), + GNUNET_TIME_UNIT_SECONDS); + pos->is = is; + pos->ogh = TALER_MERCHANT_wallet_order_get (is->ctx, + pos->merchant_url, + order_id, + h_contract, + pos->timeout, + NULL, + pos->wait_for_refund + ? &pos->refund_threshold + : NULL, + pos->wait_for_refund, + &wallet_poll_order_cb, + pos); + GNUNET_assert (NULL != pos->ogh); + /* We CONTINUE to run the interpreter while the long-polled command + completes asynchronously! */ + TALER_TESTING_interpreter_next (pos->is); +} + + +/** + * Free the state of a "GET order" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd command being run. + */ +static void +wallet_poll_order_start_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct WalletPollOrderStartState *pos = cls; + + if (NULL != pos->ogh) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Command `%s' was not terminated\n", + TALER_TESTING_interpreter_get_current_label ( + pos->is)); + TALER_MERCHANT_wallet_order_get_cancel (pos->ogh); + } + GNUNET_free (pos); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_wallet_poll_order_start ( + const char *label, + const char *merchant_url, + const char *order_ref, + struct GNUNET_TIME_Relative timeout, + const struct TALER_Amount *await_refund) +{ + struct WalletPollOrderStartState *pos; + + pos = GNUNET_new (struct WalletPollOrderStartState); + pos->order_ref = order_ref; + pos->merchant_url = merchant_url; + pos->timeout = timeout; + if (NULL != await_refund) + { + pos->wait_for_refund = true; + pos->refund_threshold = *await_refund; + } + { + struct TALER_TESTING_Command cmd = { + .cls = pos, + .label = label, + .run = &wallet_poll_order_start_run, + .cleanup = &wallet_poll_order_start_cleanup + }; + + return cmd; + } +} + + +/** + * Run the "GET order conclude" CMD. + * + * @param cls closure. + * @param cmd command being run now. + * @param is interpreter state. + */ +static void +wallet_poll_order_conclude_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct WalletPollOrderConcludeState *poc = cls; + const struct TALER_TESTING_Command *poll_cmd; + struct WalletPollOrderStartState *pos; + + poc->is = is; + poll_cmd = + TALER_TESTING_interpreter_lookup_command (is, + poc->start_reference); + if (NULL == poll_cmd) + TALER_TESTING_FAIL (poc->is); + GNUNET_assert (poll_cmd->run == &wallet_poll_order_start_run); + pos = poll_cmd->cls; + pos->cs = poc; + if (NULL == pos->ogh) + poc->task = GNUNET_SCHEDULER_add_now (&conclude_task, + poc); + else + poc->task = GNUNET_SCHEDULER_add_at (pos->deadline, + &conclude_task, + poc); +} + + +/** + * Free the state of a "GET order" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd command being run. + */ +static void +wallet_poll_order_conclude_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct WalletPollOrderConcludeState *poc = cls; + + if (NULL != poc->task) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Command `%s' was not terminated\n", + TALER_TESTING_interpreter_get_current_label ( + poc->is)); + GNUNET_SCHEDULER_cancel (poc->task); + poc->task = NULL; + } + GNUNET_free (poc); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_wallet_poll_order_conclude ( + const char *label, + unsigned int expected_http_status, + const char *expected_refund_amount, + const char *poll_start_reference) +{ + struct WalletPollOrderConcludeState *cps; + + cps = GNUNET_new (struct WalletPollOrderConcludeState); + cps->start_reference = poll_start_reference; + cps->expected_http_status = expected_http_status; + if (NULL != expected_refund_amount) + { + cps->expected_refund = true; + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (expected_refund_amount, + &cps->expected_refund_amount)); + } + { + struct TALER_TESTING_Command cmd = { + .cls = cps, + .label = label, + .run = &wallet_poll_order_conclude_run, + .cleanup = &wallet_poll_order_conclude_cleanup + }; + + return cmd; + } +} + + /* end of testing_api_cmd_wallet_get_order.c */ -- cgit v1.2.3