testing_api_cmd_post_using_templates.c (20173B)
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 const struct TALER_ClaimTokenP *resp_token; 285 286 order_cmd = TALER_TESTING_interpreter_lookup_command ( 287 tis->is, 288 tis->duplicate_of); 289 if (GNUNET_OK != 290 TALER_TESTING_get_trait_claim_token (order_cmd, 291 &prev_token)) 292 { 293 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 294 "Could not fetch previous order claim token\n"); 295 TALER_TESTING_interpreter_fail (tis->is); 296 return; 297 } 298 299 resp_token = (NULL != por->details.ok.token) 300 ? por->details.ok.token 301 : &zero_token; 302 if (0 != GNUNET_memcmp (prev_token, 303 resp_token)) 304 { 305 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 306 "Claim tokens for identical requests do not match\n"); 307 TALER_TESTING_interpreter_fail (tis->is); 308 return; 309 } 310 } 311 break; 312 case MHD_HTTP_NOT_FOUND: 313 TALER_TESTING_interpreter_next (tis->is); 314 return; 315 case MHD_HTTP_GONE: 316 TALER_TESTING_interpreter_next (tis->is); 317 return; 318 case MHD_HTTP_CONFLICT: 319 TALER_TESTING_interpreter_next (tis->is); 320 return; 321 default: 322 { 323 char *s = json_dumps (por->hr.reply, 324 JSON_COMPACT); 325 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 326 "Unexpected status code from /orders: %u (%d) at %s; JSON: %s\n", 327 por->hr.http_status, 328 (int) por->hr.ec, 329 TALER_TESTING_interpreter_get_current_label (tis->is), 330 s); 331 GNUNET_free (s); 332 /** 333 * Not failing, as test cases are _supposed_ 334 * to create non 200 OK situations. 335 */ 336 TALER_TESTING_interpreter_next (tis->is); 337 } 338 return; 339 } 340 341 if (! tis->with_claim) 342 { 343 TALER_TESTING_interpreter_next (tis->is); 344 return; 345 } 346 tis->och = TALER_MERCHANT_post_orders_claim_create ( 347 TALER_TESTING_interpreter_get_context (tis->is), 348 tis->merchant_url, 349 tis->order_id, 350 &tis->nonce); 351 if (NULL == tis->och) 352 TALER_TESTING_FAIL (tis->is); 353 TALER_MERCHANT_post_orders_claim_set_options ( 354 tis->och, 355 TALER_MERCHANT_post_orders_claim_option_token ( 356 &tis->claim_token)); 357 { 358 enum TALER_ErrorCode ec; 359 360 ec = TALER_MERCHANT_post_orders_claim_start (tis->och, 361 &using_claim_cb, 362 tis); 363 GNUNET_assert (TALER_EC_NONE == ec); 364 } 365 } 366 367 368 /** 369 * Run the "POST /using-templates" CMD. 370 * 371 * 372 * @param cls closure. 373 * @param cmd command being run now. 374 * @param is interpreter state. 375 */ 376 static void 377 post_using_templates_run (void *cls, 378 const struct TALER_TESTING_Command *cmd, 379 struct TALER_TESTING_Interpreter *is) 380 { 381 struct PostUsingTemplatesState *tis = cls; 382 const struct TALER_TESTING_Command *ref; 383 const char *template_id; 384 385 tis->is = is; 386 ref = TALER_TESTING_interpreter_lookup_command (is, 387 tis->template_ref); 388 if (GNUNET_OK != 389 TALER_TESTING_get_trait_template_id (ref, 390 &template_id)) 391 TALER_TESTING_FAIL (is); 392 if (NULL != tis->otp_ref) 393 { 394 ref = TALER_TESTING_interpreter_lookup_command (is, 395 tis->otp_ref); 396 if (GNUNET_OK != 397 TALER_TESTING_get_trait_otp_key (ref, 398 &tis->otp_key)) 399 TALER_TESTING_FAIL (is); 400 if (GNUNET_OK != 401 TALER_TESTING_get_trait_otp_alg (ref, 402 &tis->otp_alg)) 403 TALER_TESTING_FAIL (is); 404 } 405 GNUNET_CRYPTO_random_block (&tis->nonce, 406 sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); 407 tis->iph = TALER_MERCHANT_post_templates_create ( 408 TALER_TESTING_interpreter_get_context (is), 409 tis->merchant_url, 410 template_id); 411 GNUNET_assert (NULL != tis->iph); 412 if (NULL != tis->details) 413 { 414 TALER_MERCHANT_post_templates_set_options ( 415 tis->iph, 416 TALER_MERCHANT_post_templates_option_details ( 417 tis->details)); 418 } 419 else 420 { 421 if (NULL != tis->summary) 422 TALER_MERCHANT_post_templates_set_options ( 423 tis->iph, 424 TALER_MERCHANT_post_templates_option_summary ( 425 tis->summary)); 426 if (TALER_amount_is_valid (&tis->amount)) 427 TALER_MERCHANT_post_templates_set_options ( 428 tis->iph, 429 TALER_MERCHANT_post_templates_option_amount ( 430 &tis->amount)); 431 } 432 { 433 enum TALER_ErrorCode ec; 434 435 ec = TALER_MERCHANT_post_templates_start (tis->iph, 436 &post_using_templates_cb, 437 tis); 438 GNUNET_assert (TALER_EC_NONE == ec); 439 } 440 } 441 442 443 /** 444 * Offers information from the POST /using-templates CMD state to other 445 * commands. 446 * 447 * @param cls closure 448 * @param[out] ret result (could be anything) 449 * @param trait name of the trait 450 * @param index index number of the object to extract. 451 * @return #GNUNET_OK on success 452 */ 453 static enum GNUNET_GenericReturnValue 454 post_using_templates_traits (void *cls, 455 const void **ret, 456 const char *trait, 457 unsigned int index) 458 { 459 struct PostUsingTemplatesState *pts = cls; 460 struct TALER_TESTING_Trait traits[] = { 461 TALER_TESTING_make_trait_order_id (pts->order_id), 462 TALER_TESTING_make_trait_contract_terms (pts->contract_terms), 463 TALER_TESTING_make_trait_order_terms (pts->order_terms), 464 TALER_TESTING_make_trait_h_contract_terms (&pts->h_contract_terms), 465 TALER_TESTING_make_trait_merchant_sig (&pts->merchant_sig), 466 TALER_TESTING_make_trait_merchant_pub (&pts->merchant_pub), 467 TALER_TESTING_make_trait_claim_nonce (&pts->nonce), 468 TALER_TESTING_make_trait_claim_token (&pts->claim_token), 469 TALER_TESTING_make_trait_otp_key (pts->otp_key), 470 TALER_TESTING_make_trait_otp_alg (pts->otp_alg), 471 TALER_TESTING_trait_end (), 472 }; 473 474 (void) pts; 475 return TALER_TESTING_get_trait (traits, 476 ret, 477 trait, 478 index); 479 } 480 481 482 /** 483 * Free the state of a "POST using-template" CMD, and possibly 484 * cancel a pending operation thereof. 485 * 486 * @param cls closure. 487 * @param cmd command being run. 488 */ 489 static void 490 post_using_templates_cleanup (void *cls, 491 const struct TALER_TESTING_Command *cmd) 492 { 493 struct PostUsingTemplatesState *tis = cls; 494 495 if (NULL != tis->iph) 496 { 497 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 498 "POST /using-templates operation did not complete\n"); 499 TALER_MERCHANT_post_templates_cancel (tis->iph); 500 } 501 json_decref (tis->order_terms); 502 json_decref (tis->contract_terms); 503 json_decref (tis->details); 504 GNUNET_free (tis->order_id); 505 GNUNET_free (tis); 506 } 507 508 509 /** 510 * Mark part of the contract terms as possible to forget. 511 * 512 * @param cls pointer to the result of the forget operation. 513 * @param object_id name of the object to forget. 514 * @param parent parent of the object at @e object_id. 515 */ 516 static void 517 mark_forgettable (void *cls, 518 const char *object_id, 519 json_t *parent) 520 { 521 GNUNET_assert (GNUNET_OK == 522 TALER_JSON_contract_mark_forgettable (parent, 523 object_id)); 524 } 525 526 527 /** 528 * Constructs the json for a POST using template request. 529 * 530 * @param using_template_id the name of the using_template to add, can be NULL. 531 * @param refund_deadline the deadline for refunds on this using template. 532 * @param pay_deadline the deadline for payment on this using template. 533 * @param amount the amount this using template is for. 534 * @param[out] using_template where to write the json string. 535 */ 536 static void 537 make_order_json (const char *using_template_id, 538 struct GNUNET_TIME_Timestamp refund_deadline, 539 struct GNUNET_TIME_Timestamp pay_deadline, 540 const char *amount, 541 json_t **using_template) 542 { 543 struct GNUNET_TIME_Timestamp refund = refund_deadline; 544 struct GNUNET_TIME_Timestamp pay = pay_deadline; 545 json_t *contract_terms; 546 struct TALER_Amount tamount; 547 json_t *arr; 548 549 if (NULL != amount) 550 GNUNET_assert (GNUNET_OK == 551 TALER_string_to_amount (amount, 552 &tamount)); 553 /* Include required fields and some dummy objects to test forgetting. */ 554 arr = json_array (); 555 GNUNET_assert (NULL != arr); 556 GNUNET_assert (0 == 557 json_array_append_new ( 558 arr, 559 GNUNET_JSON_PACK ( 560 GNUNET_JSON_pack_string ( 561 "item", "speakers")))); 562 GNUNET_assert (0 == 563 json_array_append_new ( 564 arr, 565 GNUNET_JSON_PACK ( 566 GNUNET_JSON_pack_string ( 567 "item", "headphones")))); 568 GNUNET_assert (0 == 569 json_array_append_new ( 570 arr, 571 GNUNET_JSON_PACK ( 572 GNUNET_JSON_pack_string ( 573 "item", "earbuds")))); 574 contract_terms = GNUNET_JSON_PACK ( 575 GNUNET_JSON_pack_string ("summary", 576 "merchant-lib testcase"), 577 GNUNET_JSON_pack_allow_null ( 578 GNUNET_JSON_pack_string ( 579 "using_template_id", using_template_id)), 580 GNUNET_JSON_pack_conditional ( 581 NULL != amount, 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 */