testing_api_cmd_post_products.c (17708B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020 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_products.c 21 * @brief command to test POST /products 22 * @author Christian Grothoff 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 "merchant_api_common.h" 30 31 32 /** 33 * State of a "POST /products" CMD. 34 */ 35 struct PostProductsState 36 { 37 38 /** 39 * Handle for a "POST /products" request. 40 */ 41 struct TALER_MERCHANT_ProductsPostHandle *iph; 42 43 /** 44 * The interpreter state. 45 */ 46 struct TALER_TESTING_Interpreter *is; 47 48 /** 49 * Base URL of the merchant serving the request. 50 */ 51 const char *merchant_url; 52 53 /** 54 * ID of the product to run POST for. 55 */ 56 const char *product_id; 57 58 /** 59 * description of the product 60 */ 61 const char *description; 62 63 /** 64 * Map from IETF BCP 47 language tags to localized descriptions 65 */ 66 json_t *description_i18n; 67 68 /** 69 * unit in which the product is measured (liters, kilograms, packages, etc.) 70 */ 71 const char *unit; 72 73 /** 74 * the price for one @a unit of the product 75 */ 76 struct TALER_Amount price; 77 78 /** 79 * Array of unit prices (may point to @e price for single-entry usage). 80 */ 81 struct TALER_Amount *unit_prices; 82 83 /** 84 * Number of entries in @e unit_prices. 85 */ 86 size_t unit_prices_len; 87 88 /** 89 * True if @e unit_prices must be freed. 90 */ 91 bool owns_unit_prices; 92 93 /** 94 * True if we should send an explicit unit_price array. 95 */ 96 bool use_unit_price_array; 97 98 /** 99 * base64-encoded product image 100 */ 101 char *image; 102 103 /** 104 * list of taxes paid by the merchant 105 */ 106 json_t *taxes; 107 108 /** 109 * in @e units, -1 to indicate "infinite" (i.e. electronic books) 110 */ 111 int64_t total_stock; 112 113 /** 114 * Fractional stock component when fractional quantities are enabled. 115 */ 116 uint32_t total_stock_frac; 117 118 /** 119 * Fractional precision level associated with fractional quantities. 120 */ 121 uint32_t unit_precision_level; 122 123 /** 124 * whether fractional quantities are allowed for this product. 125 */ 126 bool unit_allow_fraction; 127 128 /** 129 * Cached string representation of the stock level. 130 */ 131 char unit_total_stock[64]; 132 133 /** 134 * set to true if we should use the extended fractional API. 135 */ 136 bool use_fractional; 137 138 /** 139 * where the product is in stock 140 */ 141 json_t *address; 142 143 /** 144 * Minimum age requirement to use for the product. 145 */ 146 unsigned int minimum_age; 147 148 /** 149 * when the next restocking is expected to happen, 0 for unknown, 150 */ 151 struct GNUNET_TIME_Timestamp next_restock; 152 153 /** 154 * Categories array length. 155 */ 156 unsigned int num_cats; 157 158 /** 159 * Categories array. 160 */ 161 uint64_t *cats; 162 163 /** 164 * Expected HTTP response code. 165 */ 166 unsigned int http_status; 167 168 }; 169 170 static void 171 post_products_update_unit_total_stock (struct PostProductsState *pps) 172 { 173 if (-1 == pps->total_stock) 174 { 175 pps->total_stock = INT64_MAX; 176 pps->total_stock_frac = INT32_MAX; 177 } 178 else if (! pps->unit_allow_fraction) 179 { 180 pps->total_stock_frac = 0; 181 } 182 TALER_MERCHANT_format_stock_string ((uint64_t) pps->total_stock, 183 pps->total_stock_frac, 184 pps->unit_total_stock, 185 sizeof (pps->unit_total_stock)); 186 } 187 188 189 static uint32_t 190 default_precision_from_unit (const char *unit) 191 { 192 struct PrecisionRule 193 { 194 const char *unit; 195 uint32_t precision; 196 }; 197 static const struct PrecisionRule rules[] = { 198 { "WeightUnitMg", 0 }, 199 { "SizeUnitMm", 0 }, 200 { "WeightUnitG", 1 }, 201 { "SizeUnitCm", 1 }, 202 { "SurfaceUnitMm2", 1 }, 203 { "VolumeUnitMm3", 1 }, 204 { "WeightUnitOunce", 2 }, 205 { "SizeUnitInch", 2 }, 206 { "SurfaceUnitCm2", 2 }, 207 { "VolumeUnitOunce", 2 }, 208 { "VolumeUnitInch3", 2 }, 209 { "TimeUnitHour", 2 }, 210 { "TimeUnitMonth", 2 }, 211 { "WeightUnitTon", 3 }, 212 { "WeightUnitKg", 3 }, 213 { "WeightUnitPound", 3 }, 214 { "SizeUnitM", 3 }, 215 { "SizeUnitDm", 3 }, 216 { "SizeUnitFoot", 3 }, 217 { "SurfaceUnitDm2", 3 }, 218 { "SurfaceUnitFoot2", 3 }, 219 { "VolumeUnitCm3", 3 }, 220 { "VolumeUnitLitre", 3 }, 221 { "VolumeUnitGallon", 3 }, 222 { "TimeUnitSecond", 3 }, 223 { "TimeUnitMinute", 3 }, 224 { "TimeUnitDay", 3 }, 225 { "TimeUnitWeek", 3 }, 226 { "SurfaceUnitM2", 4 }, 227 { "SurfaceUnitInch2", 4 }, 228 { "TimeUnitYear", 4 }, 229 { "VolumeUnitDm3", 5 }, 230 { "VolumeUnitFoot3", 5 }, 231 { "VolumeUnitM3", 6 } 232 }; 233 234 const size_t rules_len = sizeof (rules) / sizeof (rules[0]); 235 if (NULL == unit) 236 return 0; 237 238 for (size_t i = 0; i<rules_len; i++) 239 if (0 == strcmp (unit, 240 rules[i].unit)) 241 return rules[i].precision; 242 return 0; 243 } 244 245 246 /** 247 * Callback for a POST /products operation. 248 * 249 * @param cls closure for this function 250 * @param hr response being processed 251 */ 252 static void 253 post_products_cb (void *cls, 254 const struct TALER_MERCHANT_HttpResponse *hr) 255 { 256 struct PostProductsState *pis = cls; 257 258 pis->iph = NULL; 259 if (pis->http_status != hr->http_status) 260 { 261 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 262 "Unexpected response code %u (%d) to command %s\n", 263 hr->http_status, 264 (int) hr->ec, 265 TALER_TESTING_interpreter_get_current_label (pis->is)); 266 TALER_TESTING_interpreter_fail (pis->is); 267 return; 268 } 269 switch (hr->http_status) 270 { 271 case MHD_HTTP_NO_CONTENT: 272 break; 273 case MHD_HTTP_UNAUTHORIZED: 274 break; 275 case MHD_HTTP_FORBIDDEN: 276 break; 277 case MHD_HTTP_NOT_FOUND: 278 break; 279 case MHD_HTTP_CONFLICT: 280 break; 281 default: 282 GNUNET_break (0); 283 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 284 "Unhandled HTTP status %u for POST /products.\n", 285 hr->http_status); 286 } 287 TALER_TESTING_interpreter_next (pis->is); 288 } 289 290 291 /** 292 * Run the "POST /products" CMD. 293 * 294 * 295 * @param cls closure. 296 * @param cmd command being run now. 297 * @param is interpreter state. 298 */ 299 static void 300 post_products_run (void *cls, 301 const struct TALER_TESTING_Command *cmd, 302 struct TALER_TESTING_Interpreter *is) 303 { 304 struct PostProductsState *pis = cls; 305 306 pis->is = is; 307 if (pis->use_fractional || 308 pis->use_unit_price_array || 309 (0 < pis->num_cats)) 310 { 311 pis->iph = TALER_MERCHANT_products_post4 ( 312 TALER_TESTING_interpreter_get_context (is), 313 pis->merchant_url, 314 pis->product_id, 315 pis->description, 316 pis->description_i18n, 317 pis->unit, 318 pis->unit_prices, 319 pis->unit_prices_len, 320 pis->image, 321 pis->taxes, 322 pis->total_stock, 323 pis->total_stock_frac, 324 pis->unit_allow_fraction, 325 pis->use_fractional 326 ? &pis->unit_precision_level 327 : NULL, 328 pis->address, 329 pis->next_restock, 330 pis->minimum_age, 331 pis->num_cats, 332 pis->cats, 333 &post_products_cb, 334 pis); 335 } 336 else 337 { 338 pis->iph = TALER_MERCHANT_products_post2 ( 339 TALER_TESTING_interpreter_get_context (is), 340 pis->merchant_url, 341 pis->product_id, 342 pis->description, 343 pis->description_i18n, 344 pis->unit, 345 &pis->price, 346 pis->image, 347 pis->taxes, 348 pis->total_stock, 349 pis->address, 350 pis->next_restock, 351 pis->minimum_age, 352 &post_products_cb, 353 pis); 354 } 355 GNUNET_assert (NULL != pis->iph); 356 } 357 358 359 /** 360 * Offers information from the POST /products CMD state to other 361 * commands. 362 * 363 * @param cls closure 364 * @param[out] ret result (could be anything) 365 * @param trait name of the trait 366 * @param index index number of the object to extract. 367 * @return #GNUNET_OK on success 368 */ 369 static enum GNUNET_GenericReturnValue 370 post_products_traits (void *cls, 371 const void **ret, 372 const char *trait, 373 unsigned int index) 374 { 375 struct PostProductsState *pps = cls; 376 struct TALER_TESTING_Trait traits[] = { 377 TALER_TESTING_make_trait_product_description (pps->description), 378 TALER_TESTING_make_trait_i18n_description (pps->description_i18n), 379 TALER_TESTING_make_trait_product_unit (pps->unit), 380 TALER_TESTING_make_trait_amount (&pps->price), 381 TALER_TESTING_make_trait_product_image (pps->image), 382 TALER_TESTING_make_trait_taxes (pps->taxes), 383 TALER_TESTING_make_trait_product_stock (&pps->total_stock), 384 TALER_TESTING_make_trait_product_unit_total_stock ( 385 pps->unit_total_stock), 386 TALER_TESTING_make_trait_product_unit_precision_level ( 387 &pps->unit_precision_level), 388 TALER_TESTING_make_trait_product_unit_allow_fraction ( 389 &pps->unit_allow_fraction), 390 TALER_TESTING_make_trait_address (pps->address), 391 TALER_TESTING_make_trait_timestamp (0, 392 &pps->next_restock), 393 TALER_TESTING_make_trait_product_id (pps->product_id), 394 TALER_TESTING_trait_end (), 395 }; 396 397 return TALER_TESTING_get_trait (traits, 398 ret, 399 trait, 400 index); 401 } 402 403 404 /** 405 * Free the state of a "POST product" CMD, and possibly 406 * cancel a pending operation thereof. 407 * 408 * @param cls closure. 409 * @param cmd command being run. 410 */ 411 static void 412 post_products_cleanup (void *cls, 413 const struct TALER_TESTING_Command *cmd) 414 { 415 struct PostProductsState *pis = cls; 416 417 if (NULL != pis->iph) 418 { 419 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 420 "POST /products operation did not complete\n"); 421 TALER_MERCHANT_products_post_cancel (pis->iph); 422 } 423 if (pis->owns_unit_prices) 424 GNUNET_free (pis->unit_prices); 425 json_decref (pis->description_i18n); 426 GNUNET_free (pis->image); 427 json_decref (pis->taxes); 428 json_decref (pis->address); 429 GNUNET_free (pis->cats); 430 GNUNET_free (pis); 431 } 432 433 434 struct TALER_TESTING_Command 435 TALER_TESTING_cmd_merchant_post_products2 ( 436 const char *label, 437 const char *merchant_url, 438 const char *product_id, 439 const char *description, 440 json_t *description_i18n, 441 const char *unit, 442 const char *price, 443 const char *image, 444 json_t *taxes, 445 int64_t total_stock, 446 uint32_t minimum_age, 447 json_t *address, 448 struct GNUNET_TIME_Timestamp next_restock, 449 unsigned int http_status) 450 { 451 struct PostProductsState *pis; 452 453 GNUNET_assert ((NULL == taxes) || 454 json_is_array (taxes)); 455 GNUNET_assert ((NULL == description_i18n) || 456 json_is_object (description_i18n)); 457 pis = GNUNET_new (struct PostProductsState); 458 pis->merchant_url = merchant_url; 459 pis->product_id = product_id; 460 pis->http_status = http_status; 461 pis->description = description; 462 pis->description_i18n = description_i18n; /* ownership taken */ 463 pis->unit = unit; 464 pis->unit_precision_level = default_precision_from_unit (unit); 465 GNUNET_assert (GNUNET_OK == 466 TALER_string_to_amount (price, 467 &pis->price)); 468 pis->unit_prices = &pis->price; 469 pis->unit_prices_len = 1; 470 pis->owns_unit_prices = false; 471 pis->use_unit_price_array = false; 472 pis->image = GNUNET_strdup (image); 473 pis->taxes = taxes; /* ownership taken */ 474 pis->total_stock = total_stock; 475 pis->total_stock_frac = 0; 476 pis->unit_allow_fraction = false; 477 pis->use_fractional = false; 478 pis->minimum_age = minimum_age; 479 pis->address = address; /* ownership taken */ 480 pis->next_restock = next_restock; 481 pis->num_cats = 0; 482 pis->cats = NULL; 483 post_products_update_unit_total_stock (pis); 484 { 485 struct TALER_TESTING_Command cmd = { 486 .cls = pis, 487 .label = label, 488 .run = &post_products_run, 489 .cleanup = &post_products_cleanup, 490 .traits = &post_products_traits 491 }; 492 493 return cmd; 494 } 495 } 496 497 498 struct TALER_TESTING_Command 499 TALER_TESTING_cmd_merchant_post_products3 ( 500 const char *label, 501 const char *merchant_url, 502 const char *product_id, 503 const char *description, 504 json_t *description_i18n, 505 const char *unit, 506 const char *price, 507 const char *image, 508 json_t *taxes, 509 int64_t total_stock, 510 uint32_t total_stock_frac, 511 bool unit_allow_fraction, 512 uint32_t minimum_age, 513 json_t *address, 514 struct GNUNET_TIME_Timestamp next_restock, 515 unsigned int http_status) 516 { 517 struct TALER_TESTING_Command cmd; 518 519 cmd = TALER_TESTING_cmd_merchant_post_products2 (label, 520 merchant_url, 521 product_id, 522 description, 523 description_i18n, 524 unit, 525 price, 526 image, 527 taxes, 528 total_stock, 529 minimum_age, 530 address, 531 next_restock, 532 http_status); 533 { 534 struct PostProductsState *pis = cmd.cls; 535 536 pis->total_stock_frac = total_stock_frac; 537 pis->unit_allow_fraction = unit_allow_fraction; 538 pis->use_fractional = true; 539 post_products_update_unit_total_stock (pis); 540 } 541 return cmd; 542 } 543 544 545 struct TALER_TESTING_Command 546 TALER_TESTING_cmd_merchant_post_products_with_unit_prices ( 547 const char *label, 548 const char *merchant_url, 549 const char *product_id, 550 const char *description, 551 const char *unit, 552 const char *const *unit_prices, 553 size_t unit_prices_len, 554 unsigned int http_status) 555 { 556 struct PostProductsState *pis; 557 558 GNUNET_assert (0 < unit_prices_len); 559 GNUNET_assert (NULL != unit_prices); 560 pis = GNUNET_new (struct PostProductsState); 561 pis->merchant_url = merchant_url; 562 pis->product_id = product_id; 563 pis->http_status = http_status; 564 pis->description = description; 565 pis->description_i18n = json_pack ("{s:s}", "en", description); 566 pis->unit = unit; 567 pis->unit_precision_level = default_precision_from_unit (unit); 568 pis->unit_prices = GNUNET_new_array (unit_prices_len, 569 struct TALER_Amount); 570 pis->unit_prices_len = unit_prices_len; 571 pis->owns_unit_prices = true; 572 pis->use_unit_price_array = true; 573 for (size_t i = 0; i < unit_prices_len; i++) 574 GNUNET_assert (GNUNET_OK == 575 TALER_string_to_amount (unit_prices[i], 576 &pis->unit_prices[i])); 577 pis->price = pis->unit_prices[0]; 578 pis->image = GNUNET_strdup (""); 579 pis->taxes = json_array (); 580 pis->total_stock = 4; 581 pis->total_stock_frac = 0; 582 pis->unit_allow_fraction = false; 583 pis->use_fractional = false; 584 pis->minimum_age = 0; 585 pis->address = json_pack ("{s:s}", "street", "my street"); 586 pis->next_restock = GNUNET_TIME_UNIT_ZERO_TS; 587 pis->num_cats = 0; 588 pis->cats = NULL; 589 post_products_update_unit_total_stock (pis); 590 { 591 struct TALER_TESTING_Command cmd = { 592 .cls = pis, 593 .label = label, 594 .run = &post_products_run, 595 .cleanup = &post_products_cleanup, 596 .traits = &post_products_traits 597 }; 598 599 return cmd; 600 } 601 } 602 603 604 struct TALER_TESTING_Command 605 TALER_TESTING_cmd_merchant_post_products ( 606 const char *label, 607 const char *merchant_url, 608 const char *product_id, 609 const char *description, 610 const char *price, 611 unsigned int http_status) 612 { 613 return TALER_TESTING_cmd_merchant_post_products2 ( 614 label, 615 merchant_url, 616 product_id, 617 description, 618 json_pack ("{s:s}", "en", description), 619 "test-unit", 620 price, 621 "", 622 json_array (), 623 4, /* total stock */ 624 0, /* minimum age */ 625 json_pack ("{s:s}", "street", "my street"), 626 GNUNET_TIME_UNIT_ZERO_TS, 627 http_status); 628 } 629 630 631 struct TALER_TESTING_Command 632 TALER_TESTING_cmd_merchant_post_products_with_categories ( 633 const char *label, 634 const char *merchant_url, 635 const char *product_id, 636 const char *description, 637 const char *unit, 638 const char *price, 639 unsigned int num_cats, 640 const uint64_t *cats, 641 bool unit_allow_fraction, 642 uint32_t unit_precision_level, 643 unsigned int http_status) 644 { 645 struct TALER_TESTING_Command cmd; 646 647 cmd = TALER_TESTING_cmd_merchant_post_products2 ( 648 label, 649 merchant_url, 650 product_id, 651 description, 652 json_pack ("{s:s}", "en", description), 653 unit, 654 price, 655 "", 656 json_array (), 657 4, /* total stock */ 658 0, /* minimum age */ 659 json_pack ("{s:s}", "street", "my street"), 660 GNUNET_TIME_UNIT_ZERO_TS, 661 http_status); 662 { 663 struct PostProductsState *pis = cmd.cls; 664 665 pis->num_cats = num_cats; 666 if (0 < num_cats) 667 { 668 pis->cats = GNUNET_new_array (num_cats, 669 uint64_t); 670 for (unsigned int i = 0; i < num_cats; i++) 671 pis->cats[i] = cats[i]; 672 } 673 pis->unit_allow_fraction = unit_allow_fraction; 674 pis->unit_precision_level = unit_precision_level; 675 if (unit_allow_fraction) 676 pis->use_fractional = true; 677 } 678 return cmd; 679 } 680 681 682 /* end of testing_api_cmd_post_products.c */