testing_api_cmd_post_using_templates.c (20214B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 3, or 8 (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file src/testing/testing_api_cmd_post_using_templates.c 21 * @brief command to test POST /using-templates 22 * @author Priscilla HUANG 23 */ 24 #include "platform.h" 25 struct PostUsingTemplatesState; 26 #define TALER_MERCHANT_POST_TEMPLATES_RESULT_CLOSURE struct PostUsingTemplatesState 27 #define TALER_MERCHANT_POST_ORDERS_CLAIM_RESULT_CLOSURE struct PostUsingTemplatesState 28 #include <taler/taler_exchange_service.h> 29 #include <taler/taler_testing_lib.h> 30 #include "taler/taler_merchant_service.h" 31 #include "taler/taler_merchant_testing_lib.h" 32 #include <taler/merchant/post-templates-TEMPLATE_ID.h> 33 #include <taler/merchant/post-orders-ORDER_ID-claim.h> 34 35 36 /** 37 * State of a "POST /templates" CMD. 38 */ 39 struct PostUsingTemplatesState 40 { 41 42 /** 43 * Handle for a "POST /templates/$TEMPLATE_ID" request. 44 */ 45 struct TALER_MERCHANT_PostTemplatesHandle *iph; 46 47 /** 48 * The interpreter state. 49 */ 50 struct TALER_TESTING_Interpreter *is; 51 52 /** 53 * The (initial) POST /orders/$ID/claim operation handle. 54 * The logic is such that after an order creation, 55 * we immediately claim the order. 56 */ 57 struct TALER_MERCHANT_PostOrdersClaimHandle *och; 58 59 /** 60 * Base URL of the merchant serving the request. 61 */ 62 const char *merchant_url; 63 64 /** 65 * ID of the using template to run. 66 */ 67 const char *using_template_id; 68 69 /** 70 * Summary given by the customer. 71 */ 72 const char *summary; 73 74 /** 75 * Amount given by the customer. 76 */ 77 struct TALER_Amount amount; 78 79 /** 80 * Raw request body for inventory templates (if set). 81 */ 82 json_t *details; 83 84 /** 85 * Label of a command that created the template we should use. 86 */ 87 const char *template_ref; 88 89 /** 90 * Order id. 91 */ 92 char *order_id; 93 94 /** 95 * The order id we expect the merchant to assign (if not NULL). 96 */ 97 const char *expected_order_id; 98 99 /** 100 * Contract terms obtained from the backend. 101 */ 102 json_t *contract_terms; 103 104 /** 105 * Order submitted to the backend. 106 */ 107 json_t *order_terms; 108 109 /** 110 * Contract terms hash code. 111 */ 112 struct TALER_PrivateContractHashP h_contract_terms; 113 114 /** 115 * Merchant signature over the orders. 116 */ 117 struct TALER_MerchantSignatureP merchant_sig; 118 119 /** 120 * Merchant public key. 121 */ 122 struct TALER_MerchantPublicKeyP merchant_pub; 123 124 /** 125 * The nonce. 126 */ 127 struct GNUNET_CRYPTO_EddsaPublicKey nonce; 128 129 /** 130 * The claim token 131 */ 132 struct TALER_ClaimTokenP claim_token; 133 134 /** 135 * Should the command also CLAIM the order? 136 */ 137 bool with_claim; 138 139 /** 140 * If not NULL, the command should duplicate the request and verify the 141 * response is the same as in this command. 142 */ 143 const char *duplicate_of; 144 145 /** 146 * Label of command creating/updating OTP device, or NULL. 147 */ 148 const char *otp_ref; 149 150 /** 151 * Encoded key for the payment verification. 152 */ 153 const char *otp_key; 154 155 /** 156 * Option that add amount of the order 157 */ 158 const enum TALER_MerchantConfirmationAlgorithm *otp_alg; 159 160 /** 161 * Expected HTTP response code. 162 */ 163 unsigned int http_status; 164 165 }; 166 167 /** 168 * Used to fill the "using_template" CMD state with backend-provided 169 * values. Also double-checks that the using_template was correctly 170 * created. 171 * 172 * @param cls closure 173 * @param ocr response we got 174 */ 175 static void 176 using_claim_cb (struct PostUsingTemplatesState *tis, 177 const struct TALER_MERCHANT_PostOrdersClaimResponse *ocr) 178 { 179 const char *error_name; 180 unsigned int error_line; 181 struct GNUNET_JSON_Specification spec[] = { 182 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 183 &tis->merchant_pub), 184 GNUNET_JSON_spec_end () 185 }; 186 187 tis->och = NULL; 188 if (tis->http_status != ocr->hr.http_status) 189 { 190 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 191 "Expected status %u, got %u\n", 192 tis->http_status, 193 ocr->hr.http_status); 194 TALER_TESTING_FAIL (tis->is); 195 } 196 if (MHD_HTTP_OK != ocr->hr.http_status) 197 { 198 TALER_TESTING_interpreter_next (tis->is); 199 return; 200 } 201 tis->contract_terms = json_deep_copy ( 202 (json_t *) ocr->details.ok.contract_terms); 203 if (GNUNET_OK != 204 TALER_JSON_contract_hash (tis->contract_terms, 205 &tis->h_contract_terms)) 206 { 207 GNUNET_break (0); 208 TALER_TESTING_FAIL (tis->is); 209 } 210 tis->merchant_sig = ocr->details.ok.merchant_sig; 211 if (GNUNET_OK != 212 GNUNET_JSON_parse (tis->contract_terms, 213 spec, 214 &error_name, 215 &error_line)) 216 { 217 char *log; 218 219 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 220 "Parser failed on %s:%u\n", 221 error_name, 222 error_line); 223 log = json_dumps (tis->contract_terms, 224 JSON_INDENT (1)); 225 fprintf (stderr, 226 "%s\n", 227 log); 228 free (log); 229 TALER_TESTING_FAIL (tis->is); 230 } 231 TALER_TESTING_interpreter_next (tis->is); 232 } 233 234 235 /** 236 * Callback for a POST /using-templates operation. 237 * 238 * @param cls closure for this function 239 * @param por response being processed 240 */ 241 static void 242 post_using_templates_cb (struct PostUsingTemplatesState *tis, 243 const struct TALER_MERCHANT_PostTemplatesResponse *por) 244 { 245 246 tis->iph = NULL; 247 if (tis->http_status != por->hr.http_status) 248 { 249 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 250 "Unexpected response code %u (%d) to command %s\n", 251 por->hr.http_status, 252 (int) por->hr.ec, 253 TALER_TESTING_interpreter_get_current_label (tis->is)); 254 TALER_TESTING_interpreter_fail (tis->is); 255 return; 256 } 257 if (0 == tis->http_status) 258 { 259 TALER_LOG_DEBUG ("/using_templates, expected 0 status code\n"); 260 TALER_TESTING_interpreter_next (tis->is); 261 return; 262 } 263 // check for order 264 switch (por->hr.http_status) 265 { 266 case MHD_HTTP_OK: 267 if (NULL != por->details.ok.token) 268 tis->claim_token = *por->details.ok.token; 269 tis->order_id = GNUNET_strdup (por->details.ok.order_id); 270 if ((NULL != tis->expected_order_id) && 271 (0 != strcmp (por->details.ok.order_id, 272 tis->expected_order_id))) 273 { 274 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 275 "Order id assigned does not match\n"); 276 TALER_TESTING_interpreter_fail (tis->is); 277 return; 278 } 279 if (NULL != tis->duplicate_of) 280 { 281 const struct TALER_TESTING_Command *order_cmd; 282 const struct TALER_ClaimTokenP *prev_token; 283 struct TALER_ClaimTokenP zero_token = {0}; 284 285 order_cmd = TALER_TESTING_interpreter_lookup_command ( 286 tis->is, 287 tis->duplicate_of); 288 if (GNUNET_OK != 289 TALER_TESTING_get_trait_claim_token (order_cmd, 290 &prev_token)) 291 { 292 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 293 "Could not fetch previous order claim token\n"); 294 TALER_TESTING_interpreter_fail (tis->is); 295 return; 296 } 297 if (NULL == por->details.ok.token) 298 prev_token = &zero_token; 299 if (0 != GNUNET_memcmp (prev_token, 300 por->details.ok.token)) 301 { 302 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 303 "Claim tokens for identical requests do not match\n"); 304 TALER_TESTING_interpreter_fail (tis->is); 305 return; 306 } 307 } 308 break; 309 case MHD_HTTP_NOT_FOUND: 310 TALER_TESTING_interpreter_next (tis->is); 311 return; 312 case MHD_HTTP_GONE: 313 TALER_TESTING_interpreter_next (tis->is); 314 return; 315 case MHD_HTTP_CONFLICT: 316 TALER_TESTING_interpreter_next (tis->is); 317 return; 318 default: 319 { 320 char *s = json_dumps (por->hr.reply, 321 JSON_COMPACT); 322 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 323 "Unexpected status code from /orders: %u (%d) at %s; JSON: %s\n", 324 por->hr.http_status, 325 (int) por->hr.ec, 326 TALER_TESTING_interpreter_get_current_label (tis->is), 327 s); 328 GNUNET_free (s); 329 /** 330 * Not failing, as test cases are _supposed_ 331 * to create non 200 OK situations. 332 */ 333 TALER_TESTING_interpreter_next (tis->is); 334 } 335 return; 336 } 337 338 if (! tis->with_claim) 339 { 340 TALER_TESTING_interpreter_next (tis->is); 341 return; 342 } 343 tis->och = TALER_MERCHANT_post_orders_claim_create ( 344 TALER_TESTING_interpreter_get_context (tis->is), 345 tis->merchant_url, 346 tis->order_id, 347 &tis->nonce); 348 if (NULL == tis->och) 349 TALER_TESTING_FAIL (tis->is); 350 TALER_MERCHANT_post_orders_claim_set_options ( 351 tis->och, 352 TALER_MERCHANT_post_orders_claim_option_token ( 353 &tis->claim_token)); 354 { 355 enum TALER_ErrorCode ec; 356 357 ec = TALER_MERCHANT_post_orders_claim_start (tis->och, 358 &using_claim_cb, 359 tis); 360 GNUNET_assert (TALER_EC_NONE == ec); 361 } 362 } 363 364 365 /** 366 * Run the "POST /using-templates" CMD. 367 * 368 * 369 * @param cls closure. 370 * @param cmd command being run now. 371 * @param is interpreter state. 372 */ 373 static void 374 post_using_templates_run (void *cls, 375 const struct TALER_TESTING_Command *cmd, 376 struct TALER_TESTING_Interpreter *is) 377 { 378 struct PostUsingTemplatesState *tis = cls; 379 const struct TALER_TESTING_Command *ref; 380 const char *template_id; 381 382 tis->is = is; 383 ref = TALER_TESTING_interpreter_lookup_command (is, 384 tis->template_ref); 385 if (GNUNET_OK != 386 TALER_TESTING_get_trait_template_id (ref, 387 &template_id)) 388 TALER_TESTING_FAIL (is); 389 if (NULL != tis->otp_ref) 390 { 391 ref = TALER_TESTING_interpreter_lookup_command (is, 392 tis->otp_ref); 393 if (GNUNET_OK != 394 TALER_TESTING_get_trait_otp_key (ref, 395 &tis->otp_key)) 396 TALER_TESTING_FAIL (is); 397 if (GNUNET_OK != 398 TALER_TESTING_get_trait_otp_alg (ref, 399 &tis->otp_alg)) 400 TALER_TESTING_FAIL (is); 401 } 402 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, 403 &tis->nonce, 404 sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); 405 tis->iph = TALER_MERCHANT_post_templates_create ( 406 TALER_TESTING_interpreter_get_context (is), 407 tis->merchant_url, 408 template_id); 409 GNUNET_assert (NULL != tis->iph); 410 if (NULL != tis->details) 411 { 412 TALER_MERCHANT_post_templates_set_options ( 413 tis->iph, 414 TALER_MERCHANT_post_templates_option_details ( 415 tis->details)); 416 } 417 else 418 { 419 if (NULL != tis->summary) 420 TALER_MERCHANT_post_templates_set_options ( 421 tis->iph, 422 TALER_MERCHANT_post_templates_option_summary ( 423 tis->summary)); 424 if (TALER_amount_is_valid (&tis->amount)) 425 TALER_MERCHANT_post_templates_set_options ( 426 tis->iph, 427 TALER_MERCHANT_post_templates_option_amount ( 428 &tis->amount)); 429 } 430 { 431 enum TALER_ErrorCode ec; 432 433 ec = TALER_MERCHANT_post_templates_start (tis->iph, 434 &post_using_templates_cb, 435 tis); 436 GNUNET_assert (TALER_EC_NONE == ec); 437 } 438 } 439 440 441 /** 442 * Offers information from the POST /using-templates CMD state to other 443 * commands. 444 * 445 * @param cls closure 446 * @param[out] ret result (could be anything) 447 * @param trait name of the trait 448 * @param index index number of the object to extract. 449 * @return #GNUNET_OK on success 450 */ 451 static enum GNUNET_GenericReturnValue 452 post_using_templates_traits (void *cls, 453 const void **ret, 454 const char *trait, 455 unsigned int index) 456 { 457 struct PostUsingTemplatesState *pts = cls; 458 struct TALER_TESTING_Trait traits[] = { 459 TALER_TESTING_make_trait_order_id (pts->order_id), 460 TALER_TESTING_make_trait_contract_terms (pts->contract_terms), 461 TALER_TESTING_make_trait_order_terms (pts->order_terms), 462 TALER_TESTING_make_trait_h_contract_terms (&pts->h_contract_terms), 463 TALER_TESTING_make_trait_merchant_sig (&pts->merchant_sig), 464 TALER_TESTING_make_trait_merchant_pub (&pts->merchant_pub), 465 TALER_TESTING_make_trait_claim_nonce (&pts->nonce), 466 TALER_TESTING_make_trait_claim_token (&pts->claim_token), 467 TALER_TESTING_make_trait_otp_key (pts->otp_key), 468 TALER_TESTING_make_trait_otp_alg (pts->otp_alg), 469 TALER_TESTING_trait_end (), 470 }; 471 472 (void) pts; 473 return TALER_TESTING_get_trait (traits, 474 ret, 475 trait, 476 index); 477 } 478 479 480 /** 481 * Free the state of a "POST using-template" CMD, and possibly 482 * cancel a pending operation thereof. 483 * 484 * @param cls closure. 485 * @param cmd command being run. 486 */ 487 static void 488 post_using_templates_cleanup (void *cls, 489 const struct TALER_TESTING_Command *cmd) 490 { 491 struct PostUsingTemplatesState *tis = cls; 492 493 if (NULL != tis->iph) 494 { 495 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 496 "POST /using-templates operation did not complete\n"); 497 TALER_MERCHANT_post_templates_cancel (tis->iph); 498 } 499 json_decref (tis->order_terms); 500 json_decref (tis->contract_terms); 501 json_decref (tis->details); 502 GNUNET_free (tis->order_id); 503 GNUNET_free (tis); 504 } 505 506 507 /** 508 * Mark part of the contract terms as possible to forget. 509 * 510 * @param cls pointer to the result of the forget operation. 511 * @param object_id name of the object to forget. 512 * @param parent parent of the object at @e object_id. 513 */ 514 static void 515 mark_forgettable (void *cls, 516 const char *object_id, 517 json_t *parent) 518 { 519 GNUNET_assert (GNUNET_OK == 520 TALER_JSON_contract_mark_forgettable (parent, 521 object_id)); 522 } 523 524 525 /** 526 * Constructs the json for a POST using template request. 527 * 528 * @param using_template_id the name of the using_template to add, can be NULL. 529 * @param refund_deadline the deadline for refunds on this using template. 530 * @param pay_deadline the deadline for payment on this using template. 531 * @param amount the amount this using template is for. 532 * @param[out] using_template where to write the json string. 533 */ 534 static void 535 make_order_json (const char *using_template_id, 536 struct GNUNET_TIME_Timestamp refund_deadline, 537 struct GNUNET_TIME_Timestamp pay_deadline, 538 const char *amount, 539 json_t **using_template) 540 { 541 struct GNUNET_TIME_Timestamp refund = refund_deadline; 542 struct GNUNET_TIME_Timestamp pay = pay_deadline; 543 json_t *contract_terms; 544 struct TALER_Amount tamount; 545 json_t *arr; 546 547 if (NULL != amount) 548 GNUNET_assert (GNUNET_OK == 549 TALER_string_to_amount (amount, 550 &tamount)); 551 /* Include required fields and some dummy objects to test forgetting. */ 552 arr = json_array (); 553 GNUNET_assert (NULL != arr); 554 GNUNET_assert (0 == 555 json_array_append_new ( 556 arr, 557 GNUNET_JSON_PACK ( 558 GNUNET_JSON_pack_string ( 559 "item", "speakers")))); 560 GNUNET_assert (0 == 561 json_array_append_new ( 562 arr, 563 GNUNET_JSON_PACK ( 564 GNUNET_JSON_pack_string ( 565 "item", "headphones")))); 566 GNUNET_assert (0 == 567 json_array_append_new ( 568 arr, 569 GNUNET_JSON_PACK ( 570 GNUNET_JSON_pack_string ( 571 "item", "earbuds")))); 572 contract_terms = GNUNET_JSON_PACK ( 573 GNUNET_JSON_pack_string ("summary", 574 "merchant-lib testcase"), 575 GNUNET_JSON_pack_allow_null ( 576 GNUNET_JSON_pack_string ( 577 "using_template_id", using_template_id)), 578 NULL == amount 579 ? GNUNET_JSON_pack_allow_null ( 580 GNUNET_JSON_pack_string ("amount", 581 NULL)) 582 : TALER_JSON_pack_amount ("amount", 583 &tamount), 584 GNUNET_JSON_pack_string ("fulfillment_url", 585 "https://example.com"), 586 GNUNET_JSON_pack_allow_null ( 587 GNUNET_JSON_pack_timestamp ("refund_deadline", 588 refund)), 589 GNUNET_JSON_pack_allow_null ( 590 GNUNET_JSON_pack_timestamp ("pay_deadline", 591 pay)), 592 GNUNET_JSON_pack_string ("dummy_obj", 593 "EUR:1.0"), 594 GNUNET_JSON_pack_array_steal ("dummy_array", 595 arr)); 596 GNUNET_assert (GNUNET_OK == 597 TALER_JSON_expand_path (contract_terms, 598 "$.dummy_obj", 599 &mark_forgettable, 600 NULL)); 601 GNUNET_assert (GNUNET_OK == 602 TALER_JSON_expand_path (contract_terms, 603 "$.dummy_array[*].item", 604 &mark_forgettable, 605 NULL)); 606 *using_template = contract_terms; 607 } 608 609 610 struct TALER_TESTING_Command 611 TALER_TESTING_cmd_merchant_post_using_templates ( 612 const char *label, 613 const char *template_ref, 614 const char *otp_ref, 615 const char *merchant_url, 616 const char *using_template_id, 617 const char *summary, 618 const char *amount, 619 struct GNUNET_TIME_Timestamp refund_deadline, 620 struct GNUNET_TIME_Timestamp pay_deadline, 621 unsigned int http_status) 622 { 623 struct PostUsingTemplatesState *tis; 624 625 tis = GNUNET_new (struct PostUsingTemplatesState); 626 tis->template_ref = template_ref; 627 tis->otp_ref = otp_ref; 628 tis->merchant_url = merchant_url; 629 tis->using_template_id = using_template_id; 630 tis->http_status = http_status; 631 tis->summary = summary; 632 tis->with_claim = true; 633 make_order_json (using_template_id, 634 refund_deadline, 635 pay_deadline, 636 amount, 637 &tis->order_terms); 638 if (NULL != amount) 639 GNUNET_assert (GNUNET_OK == 640 TALER_string_to_amount (amount, 641 &tis->amount)); 642 { 643 struct TALER_TESTING_Command cmd = { 644 .cls = tis, 645 .label = label, 646 .run = &post_using_templates_run, 647 .cleanup = &post_using_templates_cleanup, 648 .traits = &post_using_templates_traits 649 }; 650 651 return cmd; 652 } 653 } 654 655 656 struct TALER_TESTING_Command 657 TALER_TESTING_cmd_merchant_post_using_templates2 ( 658 const char *label, 659 const char *template_ref, 660 const char *otp_ref, 661 const char *merchant_url, 662 const char *using_template_id, 663 const json_t *details, 664 unsigned int http_status) 665 { 666 struct PostUsingTemplatesState *tis; 667 668 tis = GNUNET_new (struct PostUsingTemplatesState); 669 tis->template_ref = template_ref; 670 tis->otp_ref = otp_ref; 671 tis->merchant_url = merchant_url; 672 tis->using_template_id = using_template_id; 673 tis->http_status = http_status; 674 tis->with_claim = true; 675 if (NULL != details) 676 tis->details = json_incref ((json_t *) details); 677 { 678 struct TALER_TESTING_Command cmd = { 679 .cls = tis, 680 .label = label, 681 .run = &post_using_templates_run, 682 .cleanup = &post_using_templates_cleanup, 683 .traits = &post_using_templates_traits 684 }; 685 686 return cmd; 687 } 688 } 689 690 691 /* end of testing_api_cmd_post_using_templates.c */