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