diff options
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-orders.c | 90 | ||||
-rw-r--r-- | src/include/taler_merchant_testing_lib.h | 5 | ||||
-rw-r--r-- | src/testing/test_merchant_api.c | 19 | ||||
-rw-r--r-- | src/testing/testing_api_cmd_post_orders.c | 81 |
4 files changed, 151 insertions, 44 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index 133d1674..6fd65ae8 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -417,6 +417,63 @@ execute_order (struct MHD_Connection *connection, "order:products"); } + /* Test if we already have an order with this id */ + { + struct TALER_ClaimTokenP token; + json_t *contract_terms; + TMH_db->preflight (TMH_db->cls); + qs = TMH_db->lookup_order (TMH_db->cls, + hc->instance->settings.id, + order_id, + &token, + &contract_terms); + /* If yes, check for idempotency */ + if (0 > qs) + { + TMH_db->rollback (TMH_db->cls); + return qs; + } + else if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + /* Comparing the contract terms is sufficient because all the other + params get added to it at some point. */ + if (1 == json_equal (order, + contract_terms)) + { + MHD_RESULT ret; + + ret = TALER_MHD_reply_json_pack ( + connection, + MHD_HTTP_OK, + "{s:s, s:o?}", + "order_id", + order_id, + "token", + (0 == GNUNET_is_zero (&token)) + ? NULL + : GNUNET_JSON_from_data_auto (&token)); + GNUNET_JSON_parse_free (spec); + return ret; + } + else + { + /* This request is not idempotent */ + int rv; + char *msg; + + GNUNET_JSON_parse_free (spec); + GNUNET_asprintf (&msg, + "order ID `%s' already exists, and the request was not idempotent", + order_id); + rv = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, /* or conflict? */ + TALER_EC_PROPOSAL_STORE_DB_ERROR_ALREADY_EXISTS, + msg); + GNUNET_free (msg); + return rv; + } + } + } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Executing database transaction to create order '%s' for instance '%s'\n", order_id, @@ -476,39 +533,6 @@ execute_order (struct MHD_Connection *connection, pd.next_restock)); } - { - /* Hard error could be constraint violation, - check if order already exists */ - TMH_db->preflight (TMH_db->cls); - qs = TMH_db->lookup_order (TMH_db->cls, - settings->id, - order_id, - NULL, - NULL); - if (0 < qs) - { - /* Yep, indeed uniqueness constraint violation */ - int rv; - char *msg; - - GNUNET_JSON_parse_free (spec); - GNUNET_asprintf (&msg, - "order ID `%s' already exists", - order_id); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Order `%s' already exists\n", - order_id); - /* contract_terms may be private, only expose - * duplicate order_id to the network */ - rv = TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, /* or conflict? */ - TALER_EC_PROPOSAL_STORE_DB_ERROR_ALREADY_EXISTS, - msg); - GNUNET_free (msg); - return rv; - } - } - /* Other hard transaction error (disk full, etc.) */ GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h index edbb6add..a9aa2b48 100644 --- a/src/include/taler_merchant_testing_lib.h +++ b/src/include/taler_merchant_testing_lib.h @@ -533,6 +533,8 @@ TALER_TESTING_cmd_merchant_post_orders_no_claim (const char *label, * "[product_id]/[quantity];...". * @param locks a string of references to lock product commands that should * be formatted as "[lock_1];[lock_2];...". + * @param duplicate_of if not NULL, a reference to a previous order command + * that should be duplicated and checked for an identical response. * @return the command */ struct TALER_TESTING_Command @@ -548,7 +550,8 @@ TALER_TESTING_cmd_merchant_post_orders2 (const char *label, const char *amount, const char *payment_target, const char *products, - const char *locks); + const char *locks, + const char *duplicate_of); /** diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c index b8ef13c0..b4e06ed1 100644 --- a/src/testing/test_merchant_api.c +++ b/src/testing/test_merchant_api.c @@ -285,8 +285,8 @@ run (void *cls, "EUR:5.0", "x-taler-bank", "", - ""), - /* + "", + NULL), TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-1-idem", merchant_url, MHD_HTTP_OK, @@ -297,7 +297,8 @@ run (void *cls, "EUR:5.0", "x-taler-bank", "", - ""),*/ + "", + "create-proposal-1"), TALER_TESTING_cmd_merchant_claim_order ("reclaim-1", merchant_url, MHD_HTTP_OK, @@ -520,7 +521,8 @@ run (void *cls, "EUR:5.0", "unsupported-wire-method", "product-3/2", - ""), + "", + NULL), TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-p3-pd-nx", merchant_url, MHD_HTTP_NOT_FOUND, @@ -531,7 +533,8 @@ run (void *cls, "EUR:5.0", "x-taler-bank", "unknown-product/2", - ""), + "", + NULL), TALER_TESTING_cmd_merchant_post_orders2 ( "create-proposal-p3-not-enough-stock", merchant_url, @@ -543,7 +546,8 @@ run (void *cls, "EUR:5.0", "x-taler-bank", "product-3/24", - ""), + "", + NULL), TALER_TESTING_cmd_merchant_post_orders2 ("create-proposal-p3", merchant_url, MHD_HTTP_OK, @@ -554,7 +558,8 @@ run (void *cls, "EUR:5.0", "x-taler-bank", "product-3/3", - "lock-product-p3"), + "lock-product-p3", + NULL), TALER_TESTING_cmd_merchant_delete_order ("delete-order-1", merchant_url, "1", diff --git a/src/testing/testing_api_cmd_post_orders.c b/src/testing/testing_api_cmd_post_orders.c index f95d94ae..f7a8dbb8 100644 --- a/src/testing/testing_api_cmd_post_orders.c +++ b/src/testing/testing_api_cmd_post_orders.c @@ -51,6 +51,11 @@ struct OrdersState const 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; @@ -126,6 +131,12 @@ struct OrdersState * 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; }; @@ -154,6 +165,7 @@ orders_traits (void *cls, TALER_TESTING_make_trait_merchant_pub (0, &ps->merchant_pub), TALER_TESTING_make_trait_claim_nonce (0, &ps->nonce), TALER_TESTING_make_trait_claim_token (0, &ps->claim_token), + TALER_TESTING_make_trait_string (0, ps->order), TALER_TESTING_trait_end () }; @@ -262,6 +274,44 @@ order_cb (void *cls, { case MHD_HTTP_OK: ps->order_id = GNUNET_strdup (order_id); + if ((NULL != ps->expected_order_id) && + (0 != strcmp (order_id, + ps->expected_order_id))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Order id assigned does not match\n"); + TALER_TESTING_interpreter_fail (ps->is); + return; + } + if (NULL != ps->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 ( + ps->is, + ps->duplicate_of); + if (GNUNET_OK != + TALER_TESTING_get_trait_claim_token (order_cmd, + 0, + &prev_token)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not fetch previous order claim token\n"); + TALER_TESTING_interpreter_fail (ps->is); + return; + } + if (NULL == claim_token) + prev_token = &zero_token; + if (0 != GNUNET_memcmp (prev_token, + claim_token)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Claim tokens for identical requests do not match\n"); + TALER_TESTING_interpreter_fail (ps->is); + return; + } + } break; default: { @@ -372,6 +422,7 @@ orders_run2 (void *cls, struct TALER_TESTING_Interpreter *is) { struct OrdersState *ps = cls; + const char *order_str = ps->order; json_t *order; json_error_t error; @@ -385,7 +436,24 @@ orders_run2 (void *cls, unsigned int locks_length = 0; ps->is = is; - order = json_loads (ps->order, + if (NULL != ps->duplicate_of) + { + const struct TALER_TESTING_Command *order_cmd; + order_cmd = TALER_TESTING_interpreter_lookup_command ( + is, + ps->duplicate_of); + if (GNUNET_OK != + TALER_TESTING_get_trait_string (order_cmd, + 0, + &order_str)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not fetch previous order string\n"); + TALER_TESTING_interpreter_fail (is); + return; + } + } + order = json_loads (order_str, JSON_REJECT_DUPLICATES, &error); if (NULL == order) @@ -644,6 +712,7 @@ TALER_TESTING_cmd_merchant_post_orders_no_claim (const char *label, amount, &ps->order); ps->http_status = http_status; + ps->expected_order_id = order_id; ps->merchant_url = merchant_url; ps->with_claim = false; { @@ -693,6 +762,7 @@ TALER_TESTING_cmd_merchant_post_orders (const char *label, amount, &ps->order); ps->http_status = http_status; + ps->expected_order_id = order_id; ps->merchant_url = merchant_url; ps->with_claim = true; { @@ -727,6 +797,8 @@ TALER_TESTING_cmd_merchant_post_orders (const char *label, * "[product_id]/[quantity];...". * @param locks a string of references to lock product commands that should * be formatted as "[lock_1];[lock_2];...". + * @param duplicate_of if not NULL, a reference to a previous order command + * that should be duplicated and checked for an identical response. * @return the command */ struct TALER_TESTING_Command @@ -742,7 +814,8 @@ TALER_TESTING_cmd_merchant_post_orders2 (const char *label, const char *amount, const char *payment_target, const char *products, - const char *locks) + const char *locks, + const char *duplicate_of) { struct OrdersState *ps; @@ -754,12 +827,14 @@ TALER_TESTING_cmd_merchant_post_orders2 (const char *label, &ps->order); ps->http_status = http_status; + ps->expected_order_id = order_id; ps->merchant_url = merchant_url; ps->payment_target = payment_target; ps->products = products; ps->locks = locks; - ps->with_claim = true; + ps->with_claim = (NULL == duplicate_of); ps->make_claim_token = claim_token; + ps->duplicate_of = duplicate_of; { struct TALER_TESTING_Command cmd = { .cls = ps, |