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