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