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