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