diff options
Diffstat (limited to 'src/testing/testing_api_cmd_post_using_templates.c')
-rw-r--r-- | src/testing/testing_api_cmd_post_using_templates.c | 396 |
1 files changed, 382 insertions, 14 deletions
diff --git a/src/testing/testing_api_cmd_post_using_templates.c b/src/testing/testing_api_cmd_post_using_templates.c index 956a421a..7aeec33d 100644 --- a/src/testing/testing_api_cmd_post_using_templates.c +++ b/src/testing/testing_api_cmd_post_using_templates.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022-2023 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 @@ -27,6 +27,7 @@ #include "taler_merchant_service.h" #include "taler_merchant_testing_lib.h" + /** * State of a "POST /templates" CMD. */ @@ -44,11 +45,23 @@ struct PostUsingTemplatesState struct TALER_TESTING_Interpreter *is; /** + * The (initial) POST /orders/$ID/claim operation handle. + * The logic is such that after an order creation, + * we immediately claim the order. + */ + struct TALER_MERCHANT_OrderClaimHandle *och; + + /** * Base URL of the merchant serving the request. */ const char *merchant_url; /** + * ID of the using template to run. + */ + const char *using_template_id; + + /** * Summary given by the customer. */ const char *summary; @@ -64,12 +77,145 @@ struct PostUsingTemplatesState const char *template_ref; /** + * Order id. + */ + char *order_id; + + /** + * The order id we expect the merchant to assign (if not NULL). + */ + const char *expected_order_id; + + /** + * Contract terms obtained from the backend. + */ + json_t *contract_terms; + + /** + * Order submitted to the backend. + */ + json_t *order_terms; + + /** + * Contract terms hash code. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * Merchant signature over the orders. + */ + struct TALER_MerchantSignatureP merchant_sig; + + /** + * Merchant public key. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * The nonce. + */ + struct GNUNET_CRYPTO_EddsaPublicKey nonce; + + /** + * The claim token + */ + struct TALER_ClaimTokenP claim_token; + + /** + * Should the command also CLAIM the order? + */ + bool with_claim; + + /** + * If not NULL, the command should duplicate the request and verify the + * response is the same as in this command. + */ + const char *duplicate_of; + + /** + * Label of command creating/updating OTP device, or NULL. + */ + const char *otp_ref; + + /** + * Encoded key for the payment verification. + */ + const char *otp_key; + + /** + * Option that add amount of the order + */ + const enum TALER_MerchantConfirmationAlgorithm *otp_alg; + + /** * Expected HTTP response code. */ unsigned int http_status; }; +/** + * Used to fill the "using_template" CMD state with backend-provided + * values. Also double-checks that the using_template was correctly + * created. + * + * @param cls closure + * @param ocr response we got + */ +static void +using_claim_cb (void *cls, + const struct TALER_MERCHANT_OrderClaimResponse *ocr) +{ + struct PostUsingTemplatesState *tis = cls; + const char *error_name; + unsigned int error_line; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &tis->merchant_pub), + GNUNET_JSON_spec_end () + }; + + tis->och = NULL; + if (tis->http_status != ocr->hr.http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected status %u, got %u\n", + tis->http_status, + ocr->hr.http_status); + TALER_TESTING_FAIL (tis->is); + } + if (MHD_HTTP_OK != ocr->hr.http_status) + { + TALER_TESTING_interpreter_next (tis->is); + return; + } + tis->contract_terms = json_deep_copy ( + (json_t *) ocr->details.ok.contract_terms); + tis->h_contract_terms = ocr->details.ok.h_contract_terms; + tis->merchant_sig = ocr->details.ok.sig; + if (GNUNET_OK != + GNUNET_JSON_parse (tis->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 (tis->contract_terms, + JSON_INDENT (1)); + fprintf (stderr, + "%s\n", + log); + free (log); + TALER_TESTING_FAIL (tis->is); + } + TALER_TESTING_interpreter_next (tis->is); +} + /** * Callback for a POST /using-templates operation. @@ -94,22 +240,102 @@ post_using_templates_cb (void *cls, TALER_TESTING_interpreter_fail (tis->is); return; } + if (0 == tis->http_status) + { + TALER_LOG_DEBUG ("/using_templates, expected 0 status code\n"); + TALER_TESTING_interpreter_next (tis->is); + return; + } + // check for order switch (por->hr.http_status) { case MHD_HTTP_OK: - break; - case MHD_HTTP_CONFLICT: + if (NULL != por->details.ok.token) + tis->claim_token = *por->details.ok.token; + tis->order_id = GNUNET_strdup (por->details.ok.order_id); + if ((NULL != tis->expected_order_id) && + (0 != strcmp (por->details.ok.order_id, + tis->expected_order_id))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Order id assigned does not match\n"); + TALER_TESTING_interpreter_fail (tis->is); + return; + } + if (NULL != tis->duplicate_of) + { + const struct TALER_TESTING_Command *order_cmd; + const struct TALER_ClaimTokenP *prev_token; + struct TALER_ClaimTokenP zero_token = {0}; + + order_cmd = TALER_TESTING_interpreter_lookup_command ( + tis->is, + tis->duplicate_of); + if (GNUNET_OK != + TALER_TESTING_get_trait_claim_token (order_cmd, + &prev_token)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not fetch previous order claim token\n"); + TALER_TESTING_interpreter_fail (tis->is); + return; + } + if (NULL == por->details.ok.token) + prev_token = &zero_token; + if (0 != GNUNET_memcmp (prev_token, + por->details.ok.token)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Claim tokens for identical requests do not match\n"); + TALER_TESTING_interpreter_fail (tis->is); + return; + } + } break; case MHD_HTTP_NOT_FOUND: - break; + TALER_TESTING_interpreter_next (tis->is); + return; + case MHD_HTTP_GONE: + TALER_TESTING_interpreter_next (tis->is); + return; + case MHD_HTTP_CONFLICT: + TALER_TESTING_interpreter_next (tis->is); + return; default: - GNUNET_break (0); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Unhandled HTTP status %u for POST /templates/$ID.\n", - por->hr.http_status); - break; + { + char *s = json_dumps (por->hr.reply, + JSON_COMPACT); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected status code from /orders: %u (%d) at %s; JSON: %s\n", + por->hr.http_status, + (int) por->hr.ec, + TALER_TESTING_interpreter_get_current_label (tis->is), + s); + GNUNET_free (s); + /** + * Not failing, as test cases are _supposed_ + * to create non 200 OK situations. + */ + TALER_TESTING_interpreter_next (tis->is); + } + return; } - TALER_TESTING_interpreter_next (tis->is); + + if (! tis->with_claim) + { + TALER_TESTING_interpreter_next (tis->is); + return; + } + if (NULL == + (tis->och = TALER_MERCHANT_order_claim ( + TALER_TESTING_interpreter_get_context (tis->is), + tis->merchant_url, + tis->order_id, + &tis->nonce, + &tis->claim_token, + &using_claim_cb, + tis))) + TALER_TESTING_FAIL (tis->is); } @@ -128,7 +354,7 @@ post_using_templates_run (void *cls, { struct PostUsingTemplatesState *tis = cls; const struct TALER_TESTING_Command *ref; - const char **template_id; + const char *template_id; tis->is = is; ref = TALER_TESTING_interpreter_lookup_command (is, @@ -137,10 +363,23 @@ post_using_templates_run (void *cls, TALER_TESTING_get_trait_template_id (ref, &template_id)) TALER_TESTING_FAIL (is); + if (NULL != tis->otp_ref) + { + ref = TALER_TESTING_interpreter_lookup_command (is, + tis->otp_ref); + if (GNUNET_OK != + TALER_TESTING_get_trait_otp_key (ref, + &tis->otp_key)) + TALER_TESTING_FAIL (is); + if (GNUNET_OK != + TALER_TESTING_get_trait_otp_alg (ref, + &tis->otp_alg)) + TALER_TESTING_FAIL (is); + } tis->iph = TALER_MERCHANT_using_templates_post ( - is->ctx, + TALER_TESTING_interpreter_get_context (is), tis->merchant_url, - *template_id, + template_id, tis->summary, TALER_amount_is_valid (&tis->amount) ? &tis->amount @@ -169,6 +408,16 @@ post_using_templates_traits (void *cls, { struct PostUsingTemplatesState *pts = cls; struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_order_id (pts->order_id), + TALER_TESTING_make_trait_contract_terms (pts->contract_terms), + TALER_TESTING_make_trait_order_terms (pts->order_terms), + TALER_TESTING_make_trait_h_contract_terms (&pts->h_contract_terms), + TALER_TESTING_make_trait_merchant_sig (&pts->merchant_sig), + TALER_TESTING_make_trait_merchant_pub (&pts->merchant_pub), + TALER_TESTING_make_trait_claim_nonce (&pts->nonce), + TALER_TESTING_make_trait_claim_token (&pts->claim_token), + TALER_TESTING_make_trait_otp_key (pts->otp_key), + TALER_TESTING_make_trait_otp_alg (pts->otp_alg), TALER_TESTING_trait_end (), }; @@ -199,25 +448,144 @@ post_using_templates_cleanup (void *cls, "POST /using-templates operation did not complete\n"); TALER_MERCHANT_using_templates_post_cancel (tis->iph); } + json_decref (tis->order_terms); + json_decref (tis->contract_terms); + GNUNET_free (tis->order_id); GNUNET_free (tis); } +/** + * Mark part of the contract terms as possible to forget. + * + * @param cls pointer to the result of the forget operation. + * @param object_id name of the object to forget. + * @param parent parent of the object at @e object_id. + */ +static void +mark_forgettable (void *cls, + const char *object_id, + json_t *parent) +{ + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_mark_forgettable (parent, + object_id)); +} + + +/** + * Constructs the json for a POST using template request. + * + * @param using_template_id the name of the using_template to add, can be NULL. + * @param refund_deadline the deadline for refunds on this using template. + * @param pay_deadline the deadline for payment on this using template. + * @param amount the amount this using template is for. + * @param[out] using_template where to write the json string. + */ +static void +make_order_json (const char *using_template_id, + struct GNUNET_TIME_Timestamp refund_deadline, + struct GNUNET_TIME_Timestamp pay_deadline, + const char *amount, + json_t **using_template) +{ + struct GNUNET_TIME_Timestamp refund = refund_deadline; + struct GNUNET_TIME_Timestamp pay = pay_deadline; + json_t *contract_terms; + struct TALER_Amount tamount; + json_t *arr; + + if (NULL != amount) + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (amount, + &tamount)); + /* Include required fields and some dummy objects to test forgetting. */ + arr = json_array (); + GNUNET_assert (NULL != arr); + GNUNET_assert (0 == + json_array_append_new ( + arr, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "item", "speakers")))); + GNUNET_assert (0 == + json_array_append_new ( + arr, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "item", "headphones")))); + GNUNET_assert (0 == + json_array_append_new ( + arr, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "item", "earbuds")))); + contract_terms = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("summary", + "merchant-lib testcase"), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + "using_template_id", using_template_id)), + NULL == amount + ? GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("amount", + NULL)) + : TALER_JSON_pack_amount ("amount", + &tamount), + GNUNET_JSON_pack_string ("fulfillment_url", + "https://example.com"), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("refund_deadline", + refund)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("pay_deadline", + pay)), + GNUNET_JSON_pack_string ("dummy_obj", + "EUR:1.0"), + GNUNET_JSON_pack_array_steal ("dummy_array", + arr)); + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (contract_terms, + "$.dummy_obj", + &mark_forgettable, + NULL)); + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (contract_terms, + "$.dummy_array[*].item", + &mark_forgettable, + NULL)); + *using_template = contract_terms; +} + + struct TALER_TESTING_Command TALER_TESTING_cmd_merchant_post_using_templates ( const char *label, const char *template_ref, + const char *otp_ref, const char *merchant_url, + const char *using_template_id, const char *summary, - const char *amount, unsigned int http_status) + const char *amount, + struct GNUNET_TIME_Timestamp refund_deadline, + struct GNUNET_TIME_Timestamp pay_deadline, + unsigned int http_status) { struct PostUsingTemplatesState *tis; tis = GNUNET_new (struct PostUsingTemplatesState); tis->template_ref = template_ref; + tis->otp_ref = otp_ref; tis->merchant_url = merchant_url; + tis->using_template_id = using_template_id; tis->http_status = http_status; tis->summary = summary; + tis->with_claim = true; + make_order_json (using_template_id, + refund_deadline, + pay_deadline, + amount, + &tis->order_terms); if (NULL != amount) GNUNET_assert (GNUNET_OK == TALER_string_to_amount (amount, |