testing_api_cmd_patch_product.c (16117B)
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_patch_product.c 21 * @brief command to test PATCH /product 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 <taler/taler-merchant/patch-private-products-PRODUCT_ID.h> 30 31 32 /** 33 * State of a "PATCH /product" CMD. 34 */ 35 struct PatchProductState 36 { 37 38 /** 39 * Handle for a "GET product" request. 40 */ 41 struct TALER_MERCHANT_PatchPrivateProductHandle *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 GET 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 * in @e units. 140 */ 141 int64_t total_lost; 142 143 /** 144 * where the product is in stock 145 */ 146 json_t *address; 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 * Expected HTTP response code. 155 */ 156 unsigned int http_status; 157 158 }; 159 160 static void 161 patch_product_update_unit_total_stock (struct PatchProductState *pps) 162 { 163 uint64_t stock; 164 uint32_t frac; 165 166 if (-1 == pps->total_stock) 167 { 168 stock = (uint64_t) INT64_MAX; 169 frac = (uint32_t) INT32_MAX; 170 } 171 else 172 { 173 stock = (uint64_t) pps->total_stock; 174 frac = pps->unit_allow_fraction ? pps->total_stock_frac : 0; 175 } 176 TALER_MERCHANT_format_stock_string (stock, 177 frac, 178 pps->unit_total_stock, 179 sizeof (pps->unit_total_stock)); 180 } 181 182 183 static uint32_t 184 default_precision_from_unit (const char *unit) 185 { 186 struct PrecisionRule 187 { 188 const char *unit; 189 uint32_t precision; 190 }; 191 static const struct PrecisionRule rules[] = { 192 { "WeightUnitMg", 0 }, 193 { "SizeUnitMm", 0 }, 194 { "WeightUnitG", 1 }, 195 { "SizeUnitCm", 1 }, 196 { "SurfaceUnitMm2", 1 }, 197 { "VolumeUnitMm3", 1 }, 198 { "WeightUnitOunce", 2 }, 199 { "SizeUnitInch", 2 }, 200 { "SurfaceUnitCm2", 2 }, 201 { "VolumeUnitOunce", 2 }, 202 { "VolumeUnitInch3", 2 }, 203 { "TimeUnitHour", 2 }, 204 { "TimeUnitMonth", 2 }, 205 { "WeightUnitTon", 3 }, 206 { "WeightUnitKg", 3 }, 207 { "WeightUnitPound", 3 }, 208 { "SizeUnitM", 3 }, 209 { "SizeUnitDm", 3 }, 210 { "SizeUnitFoot", 3 }, 211 { "SurfaceUnitDm2", 3 }, 212 { "SurfaceUnitFoot2", 3 }, 213 { "VolumeUnitCm3", 3 }, 214 { "VolumeUnitLitre", 3 }, 215 { "VolumeUnitGallon", 3 }, 216 { "TimeUnitSecond", 3 }, 217 { "TimeUnitMinute", 3 }, 218 { "TimeUnitDay", 3 }, 219 { "TimeUnitWeek", 3 }, 220 { "SurfaceUnitM2", 4 }, 221 { "SurfaceUnitInch2", 4 }, 222 { "TimeUnitYear", 4 }, 223 { "VolumeUnitDm3", 5 }, 224 { "VolumeUnitFoot3", 5 }, 225 { "VolumeUnitM3", 6 } 226 }; 227 228 const size_t rules_len = sizeof (rules) / sizeof (rules[0]); 229 if (NULL == unit) 230 return 0; 231 232 for (size_t i = 0; i<rules_len; i++) 233 if (0 == strcmp (unit, 234 rules[i].unit)) 235 return rules[i].precision; 236 return 0; 237 } 238 239 240 /** 241 * Callback for a PATCH /products/$ID operation. 242 * 243 * @param cls closure for this function 244 * @param hr response being processed 245 */ 246 static void 247 patch_product_cb (void *cls, 248 const struct TALER_MERCHANT_PatchPrivateProductResponse * 249 result) 250 { 251 struct PatchProductState *pis = cls; 252 const struct TALER_MERCHANT_HttpResponse *hr = &result->hr; 253 254 pis->iph = NULL; 255 if (pis->http_status != hr->http_status) 256 { 257 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 258 "Unexpected response code %u (%d) to command %s\n", 259 hr->http_status, 260 (int) hr->ec, 261 TALER_TESTING_interpreter_get_current_label (pis->is)); 262 TALER_TESTING_interpreter_fail (pis->is); 263 return; 264 } 265 switch (hr->http_status) 266 { 267 case MHD_HTTP_NO_CONTENT: 268 break; 269 case MHD_HTTP_UNAUTHORIZED: 270 break; 271 case MHD_HTTP_FORBIDDEN: 272 break; 273 case MHD_HTTP_NOT_FOUND: 274 break; 275 case MHD_HTTP_CONFLICT: 276 break; 277 default: 278 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 279 "Unhandled HTTP status %u for PATCH /products/ID.\n", 280 hr->http_status); 281 } 282 TALER_TESTING_interpreter_next (pis->is); 283 } 284 285 286 /** 287 * Run the "PATCH /products/$ID" CMD. 288 * 289 * 290 * @param cls closure. 291 * @param cmd command being run now. 292 * @param is interpreter state. 293 */ 294 static void 295 patch_product_run (void *cls, 296 const struct TALER_TESTING_Command *cmd, 297 struct TALER_TESTING_Interpreter *is) 298 { 299 struct PatchProductState *pis = cls; 300 301 pis->is = is; 302 pis->iph = TALER_MERCHANT_patch_private_product_create ( 303 TALER_TESTING_interpreter_get_context (is), 304 pis->merchant_url, 305 pis->product_id, 306 pis->description, 307 pis->unit, 308 pis->unit_prices_len, 309 pis->unit_prices); 310 GNUNET_assert ( 311 GNUNET_OK == 312 TALER_MERCHANT_patch_private_product_set_options ( 313 pis->iph, 314 TALER_MERCHANT_patch_private_product_option_description_i18n (pis->description_i18n), 315 TALER_MERCHANT_patch_private_product_option_image (pis->image), 316 TALER_MERCHANT_patch_private_product_option_taxes (pis->taxes), 317 TALER_MERCHANT_patch_private_product_option_total_stock_val (pis->total_stock), 318 TALER_MERCHANT_patch_private_product_option_total_lost (pis->total_lost), 319 TALER_MERCHANT_patch_private_product_option_address (pis->address), 320 TALER_MERCHANT_patch_private_product_option_next_restock (pis->next_restock))); 321 GNUNET_assert (NULL != pis->iph); 322 if (pis->use_fractional) 323 { 324 GNUNET_assert ( 325 GNUNET_OK == 326 TALER_MERCHANT_patch_private_product_set_options ( 327 pis->iph, 328 TALER_MERCHANT_patch_private_product_option_total_stock_frac ( 329 pis->total_stock_frac), 330 TALER_MERCHANT_patch_private_product_option_unit_allow_fraction ( 331 pis->unit_allow_fraction), 332 TALER_MERCHANT_patch_private_product_option_unit_precision_level ( 333 pis->unit_precision_level))); 334 } 335 { 336 enum TALER_ErrorCode ec; 337 338 ec = TALER_MERCHANT_patch_private_product_start ( 339 pis->iph, 340 &patch_product_cb, 341 pis); 342 GNUNET_assert (TALER_EC_NONE == ec); 343 } 344 } 345 346 347 /** 348 * Offers information from the PATCH /products CMD state to other 349 * commands. 350 * 351 * @param cls closure 352 * @param[out] ret result (could be anything) 353 * @param trait name of the trait 354 * @param index index number of the object to extract. 355 * @return #GNUNET_OK on success 356 */ 357 static enum GNUNET_GenericReturnValue 358 patch_product_traits (void *cls, 359 const void **ret, 360 const char *trait, 361 unsigned int index) 362 { 363 struct PatchProductState *pps = cls; 364 struct TALER_TESTING_Trait traits[] = { 365 TALER_TESTING_make_trait_product_description (pps->description), 366 TALER_TESTING_make_trait_i18n_description (pps->description_i18n), 367 TALER_TESTING_make_trait_product_unit (pps->unit), 368 TALER_TESTING_make_trait_amount (&pps->price), 369 TALER_TESTING_make_trait_product_image (pps->image), 370 TALER_TESTING_make_trait_taxes (pps->taxes), 371 TALER_TESTING_make_trait_product_stock (&pps->total_stock), 372 TALER_TESTING_make_trait_product_unit_total_stock ( 373 pps->unit_total_stock), 374 TALER_TESTING_make_trait_product_unit_precision_level ( 375 &pps->unit_precision_level), 376 TALER_TESTING_make_trait_product_unit_allow_fraction ( 377 &pps->unit_allow_fraction), 378 TALER_TESTING_make_trait_address (pps->address), 379 TALER_TESTING_make_trait_timestamp (0, 380 &pps->next_restock), 381 TALER_TESTING_make_trait_product_id (pps->product_id), 382 TALER_TESTING_trait_end (), 383 }; 384 385 return TALER_TESTING_get_trait (traits, 386 ret, 387 trait, 388 index); 389 } 390 391 392 /** 393 * Free the state of a "GET product" CMD, and possibly 394 * cancel a pending operation thereof. 395 * 396 * @param cls closure. 397 * @param cmd command being run. 398 */ 399 static void 400 patch_product_cleanup (void *cls, 401 const struct TALER_TESTING_Command *cmd) 402 { 403 struct PatchProductState *pis = cls; 404 405 if (NULL != pis->iph) 406 { 407 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 408 "PATCH /products/$ID operation did not complete\n"); 409 TALER_MERCHANT_patch_private_product_cancel (pis->iph); 410 } 411 if (pis->owns_unit_prices) 412 GNUNET_free (pis->unit_prices); 413 json_decref (pis->description_i18n); 414 GNUNET_free (pis->image); 415 json_decref (pis->taxes); 416 json_decref (pis->address); 417 GNUNET_free (pis); 418 } 419 420 421 struct TALER_TESTING_Command 422 TALER_TESTING_cmd_merchant_patch_product ( 423 const char *label, 424 const char *merchant_url, 425 const char *product_id, 426 const char *description, 427 json_t *description_i18n, 428 const char *unit, 429 const char *price, 430 const char *image, 431 json_t *taxes, 432 int64_t total_stock, 433 uint64_t total_lost, 434 json_t *address, 435 struct GNUNET_TIME_Timestamp next_restock, 436 unsigned int http_status) 437 { 438 struct PatchProductState *pis; 439 440 GNUNET_assert ( (NULL == taxes) || 441 json_is_array (taxes)); 442 pis = GNUNET_new (struct PatchProductState); 443 pis->merchant_url = merchant_url; 444 pis->product_id = product_id; 445 pis->http_status = http_status; 446 pis->description = description; 447 pis->description_i18n = description_i18n; /* ownership taken */ 448 pis->unit = unit; 449 pis->unit_precision_level = default_precision_from_unit (unit); 450 GNUNET_assert (GNUNET_OK == 451 TALER_string_to_amount (price, 452 &pis->price)); 453 pis->unit_prices = &pis->price; 454 pis->unit_prices_len = 1; 455 pis->owns_unit_prices = false; 456 pis->use_unit_price_array = false; 457 pis->image = GNUNET_strdup (image); 458 pis->taxes = taxes; /* ownership taken */ 459 pis->total_stock = total_stock; 460 pis->total_stock_frac = 0; 461 pis->unit_allow_fraction = false; 462 pis->use_fractional = false; 463 pis->total_lost = total_lost; 464 pis->address = address; /* ownership taken */ 465 pis->next_restock = next_restock; 466 patch_product_update_unit_total_stock (pis); 467 { 468 struct TALER_TESTING_Command cmd = { 469 .cls = pis, 470 .label = label, 471 .run = &patch_product_run, 472 .cleanup = &patch_product_cleanup, 473 .traits = &patch_product_traits 474 }; 475 476 return cmd; 477 } 478 } 479 480 481 struct TALER_TESTING_Command 482 TALER_TESTING_cmd_merchant_patch_product2 ( 483 const char *label, 484 const char *merchant_url, 485 const char *product_id, 486 const char *description, 487 json_t *description_i18n, 488 const char *unit, 489 const char *price, 490 const char *image, 491 json_t *taxes, 492 int64_t total_stock, 493 uint32_t total_stock_frac, 494 bool unit_allow_fraction, 495 uint64_t total_lost, 496 json_t *address, 497 struct GNUNET_TIME_Timestamp next_restock, 498 unsigned int http_status) 499 { 500 struct TALER_TESTING_Command cmd; 501 502 cmd = TALER_TESTING_cmd_merchant_patch_product (label, 503 merchant_url, 504 product_id, 505 description, 506 description_i18n, 507 unit, 508 price, 509 image, 510 taxes, 511 total_stock, 512 total_lost, 513 address, 514 next_restock, 515 http_status); 516 { 517 struct PatchProductState *pps = cmd.cls; 518 519 pps->total_stock_frac = total_stock_frac; 520 pps->unit_allow_fraction = unit_allow_fraction; 521 pps->use_fractional = true; 522 patch_product_update_unit_total_stock (pps); 523 } 524 return cmd; 525 } 526 527 528 struct TALER_TESTING_Command 529 TALER_TESTING_cmd_merchant_patch_product_with_unit_prices ( 530 const char *label, 531 const char *merchant_url, 532 const char *product_id, 533 const char *description, 534 const char *unit, 535 const char *const *unit_prices, 536 size_t unit_prices_len, 537 unsigned int http_status) 538 { 539 struct PatchProductState *pis; 540 541 GNUNET_assert (0 < unit_prices_len); 542 GNUNET_assert (NULL != unit_prices); 543 pis = GNUNET_new (struct PatchProductState); 544 pis->merchant_url = merchant_url; 545 pis->product_id = product_id; 546 pis->http_status = http_status; 547 pis->description = description; 548 pis->description_i18n = json_pack ("{s:s}", "en", description); 549 pis->unit = unit; 550 pis->unit_precision_level = default_precision_from_unit (unit); 551 pis->unit_prices = GNUNET_new_array (unit_prices_len, 552 struct TALER_Amount); 553 pis->unit_prices_len = unit_prices_len; 554 pis->owns_unit_prices = true; 555 pis->use_unit_price_array = true; 556 for (size_t i = 0; i < unit_prices_len; i++) 557 GNUNET_assert (GNUNET_OK == 558 TALER_string_to_amount (unit_prices[i], 559 &pis->unit_prices[i])); 560 pis->price = pis->unit_prices[0]; 561 pis->image = GNUNET_strdup (""); 562 pis->taxes = json_array (); 563 pis->total_stock = 5; 564 pis->total_stock_frac = 0; 565 pis->unit_allow_fraction = true; 566 pis->unit_precision_level = 1; 567 pis->use_fractional = true; 568 pis->total_lost = 0; 569 pis->address = json_object (); 570 pis->next_restock = GNUNET_TIME_UNIT_FOREVER_TS; 571 patch_product_update_unit_total_stock (pis); 572 { 573 struct TALER_TESTING_Command cmd = { 574 .cls = pis, 575 .label = label, 576 .run = &patch_product_run, 577 .cleanup = &patch_product_cleanup, 578 .traits = &patch_product_traits 579 }; 580 581 return cmd; 582 } 583 } 584 585 586 /* end of testing_api_cmd_patch_product.c */