testing_api_cmd_post_using_templates.c (20156B)
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 (&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 */