testing_api_cmd_post_products.c (13756B)
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 "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 #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 * base64-encoded product image 80 */ 81 char *image; 82 83 /** 84 * list of taxes paid by the merchant 85 */ 86 json_t *taxes; 87 88 /** 89 * in @e units, -1 to indicate "infinite" (i.e. electronic books) 90 */ 91 int64_t total_stock; 92 93 /** 94 * Fractional stock component when fractional quantities are enabled. 95 */ 96 uint32_t total_stock_frac; 97 98 /** 99 * Fractional precision level associated with fractional quantities. 100 */ 101 uint32_t unit_precision_level; 102 103 /** 104 * whether fractional quantities are allowed for this product. 105 */ 106 bool unit_allow_fraction; 107 108 /** 109 * Cached string representation of the stock level. 110 */ 111 char unit_total_stock[64]; 112 113 /** 114 * set to true if we should use the extended fractional API. 115 */ 116 bool use_fractional; 117 118 /** 119 * where the product is in stock 120 */ 121 json_t *address; 122 123 /** 124 * Minimum age requirement to use for the product. 125 */ 126 unsigned int minimum_age; 127 128 /** 129 * when the next restocking is expected to happen, 0 for unknown, 130 */ 131 struct GNUNET_TIME_Timestamp next_restock; 132 133 /** 134 * Expected HTTP response code. 135 */ 136 unsigned int http_status; 137 138 }; 139 140 static void 141 post_products_update_unit_total_stock (struct PostProductsState *pps) 142 { 143 uint64_t stock; 144 uint32_t frac; 145 146 if (-1 == pps->total_stock) 147 { 148 stock = (uint64_t) INT64_MAX; 149 frac = (uint32_t) INT32_MAX; 150 } 151 else 152 { 153 stock = (uint64_t) pps->total_stock; 154 frac = pps->unit_allow_fraction ? pps->total_stock_frac : 0; 155 } 156 TALER_MERCHANT_format_stock_string (stock, 157 frac, 158 pps->unit_total_stock, 159 sizeof (pps->unit_total_stock)); 160 } 161 162 163 static uint32_t 164 default_precision_from_unit (const char *unit) 165 { 166 struct PrecisionRule 167 { 168 const char *unit; 169 uint32_t precision; 170 }; 171 static const struct PrecisionRule rules[] = { 172 { "WeightUnitMg", 0 }, 173 { "SizeUnitMm", 0 }, 174 { "WeightUnitG", 1 }, 175 { "SizeUnitCm", 1 }, 176 { "SurfaceUnitMm2", 1 }, 177 { "VolumeUnitMm3", 1 }, 178 { "WeightUnitOunce", 2 }, 179 { "SizeUnitInch", 2 }, 180 { "SurfaceUnitCm2", 2 }, 181 { "VolumeUnitOunce", 2 }, 182 { "VolumeUnitInch3", 2 }, 183 { "TimeUnitHour", 2 }, 184 { "TimeUnitMonth", 2 }, 185 { "WeightUnitTon", 3 }, 186 { "WeightUnitKg", 3 }, 187 { "WeightUnitPound", 3 }, 188 { "SizeUnitM", 3 }, 189 { "SizeUnitDm", 3 }, 190 { "SizeUnitFoot", 3 }, 191 { "SurfaceUnitDm2", 3 }, 192 { "SurfaceUnitFoot2", 3 }, 193 { "VolumeUnitCm3", 3 }, 194 { "VolumeUnitLitre", 3 }, 195 { "VolumeUnitGallon", 3 }, 196 { "TimeUnitSecond", 3 }, 197 { "TimeUnitMinute", 3 }, 198 { "TimeUnitDay", 3 }, 199 { "TimeUnitWeek", 3 }, 200 { "SurfaceUnitM2", 4 }, 201 { "SurfaceUnitInch2", 4 }, 202 { "TimeUnitYear", 4 }, 203 { "VolumeUnitDm3", 5 }, 204 { "VolumeUnitFoot3", 5 }, 205 { "VolumeUnitM3", 6 } 206 }; 207 208 const size_t rules_len = sizeof (rules) / sizeof (rules[0]); 209 if (NULL == unit) 210 return 0; 211 212 for (size_t i = 0; i<rules_len; i++) 213 if (0 == strcmp (unit, 214 rules[i].unit)) 215 return rules[i].precision; 216 return 0; 217 } 218 219 220 /** 221 * Callback for a POST /products operation. 222 * 223 * @param cls closure for this function 224 * @param hr response being processed 225 */ 226 static void 227 post_products_cb (void *cls, 228 const struct TALER_MERCHANT_HttpResponse *hr) 229 { 230 struct PostProductsState *pis = cls; 231 232 pis->iph = NULL; 233 if (pis->http_status != hr->http_status) 234 { 235 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 236 "Unexpected response code %u (%d) to command %s\n", 237 hr->http_status, 238 (int) hr->ec, 239 TALER_TESTING_interpreter_get_current_label (pis->is)); 240 TALER_TESTING_interpreter_fail (pis->is); 241 return; 242 } 243 switch (hr->http_status) 244 { 245 case MHD_HTTP_NO_CONTENT: 246 break; 247 case MHD_HTTP_UNAUTHORIZED: 248 break; 249 case MHD_HTTP_FORBIDDEN: 250 break; 251 case MHD_HTTP_NOT_FOUND: 252 break; 253 case MHD_HTTP_CONFLICT: 254 break; 255 default: 256 GNUNET_break (0); 257 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 258 "Unhandled HTTP status %u for POST /products.\n", 259 hr->http_status); 260 } 261 TALER_TESTING_interpreter_next (pis->is); 262 } 263 264 265 /** 266 * Run the "POST /products" CMD. 267 * 268 * 269 * @param cls closure. 270 * @param cmd command being run now. 271 * @param is interpreter state. 272 */ 273 static void 274 post_products_run (void *cls, 275 const struct TALER_TESTING_Command *cmd, 276 struct TALER_TESTING_Interpreter *is) 277 { 278 struct PostProductsState *pis = cls; 279 280 pis->is = is; 281 if (pis->use_fractional) 282 { 283 pis->iph = TALER_MERCHANT_products_post4 ( 284 TALER_TESTING_interpreter_get_context (is), 285 pis->merchant_url, 286 pis->product_id, 287 pis->description, 288 pis->description_i18n, 289 pis->unit, 290 &pis->price, 291 1, 292 pis->image, 293 pis->taxes, 294 pis->total_stock, 295 pis->total_stock_frac, 296 pis->unit_allow_fraction, 297 pis->use_fractional 298 ? &pis->unit_precision_level 299 : NULL, 300 pis->address, 301 pis->next_restock, 302 pis->minimum_age, 303 0, 304 NULL, 305 &post_products_cb, 306 pis); 307 } 308 else 309 { 310 pis->iph = TALER_MERCHANT_products_post2 ( 311 TALER_TESTING_interpreter_get_context (is), 312 pis->merchant_url, 313 pis->product_id, 314 pis->description, 315 pis->description_i18n, 316 pis->unit, 317 &pis->price, 318 pis->image, 319 pis->taxes, 320 pis->total_stock, 321 pis->address, 322 pis->next_restock, 323 pis->minimum_age, 324 &post_products_cb, 325 pis); 326 } 327 GNUNET_assert (NULL != pis->iph); 328 } 329 330 331 /** 332 * Offers information from the POST /products CMD state to other 333 * commands. 334 * 335 * @param cls closure 336 * @param[out] ret result (could be anything) 337 * @param trait name of the trait 338 * @param index index number of the object to extract. 339 * @return #GNUNET_OK on success 340 */ 341 static enum GNUNET_GenericReturnValue 342 post_products_traits (void *cls, 343 const void **ret, 344 const char *trait, 345 unsigned int index) 346 { 347 struct PostProductsState *pps = cls; 348 struct TALER_TESTING_Trait traits[] = { 349 TALER_TESTING_make_trait_product_description (pps->description), 350 TALER_TESTING_make_trait_i18n_description (pps->description_i18n), 351 TALER_TESTING_make_trait_product_unit (pps->unit), 352 TALER_TESTING_make_trait_amount (&pps->price), 353 TALER_TESTING_make_trait_product_image (pps->image), 354 TALER_TESTING_make_trait_taxes (pps->taxes), 355 TALER_TESTING_make_trait_product_stock (&pps->total_stock), 356 TALER_TESTING_make_trait_product_unit_total_stock ( 357 pps->unit_total_stock), 358 TALER_TESTING_make_trait_product_unit_precision_level ( 359 &pps->unit_precision_level), 360 TALER_TESTING_make_trait_product_unit_allow_fraction ( 361 &pps->unit_allow_fraction), 362 TALER_TESTING_make_trait_address (pps->address), 363 TALER_TESTING_make_trait_timestamp (0, 364 &pps->next_restock), 365 TALER_TESTING_make_trait_product_id (pps->product_id), 366 TALER_TESTING_trait_end (), 367 }; 368 369 return TALER_TESTING_get_trait (traits, 370 ret, 371 trait, 372 index); 373 } 374 375 376 /** 377 * Free the state of a "POST product" CMD, and possibly 378 * cancel a pending operation thereof. 379 * 380 * @param cls closure. 381 * @param cmd command being run. 382 */ 383 static void 384 post_products_cleanup (void *cls, 385 const struct TALER_TESTING_Command *cmd) 386 { 387 struct PostProductsState *pis = cls; 388 389 if (NULL != pis->iph) 390 { 391 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 392 "POST /products operation did not complete\n"); 393 TALER_MERCHANT_products_post_cancel (pis->iph); 394 } 395 json_decref (pis->description_i18n); 396 GNUNET_free (pis->image); 397 json_decref (pis->taxes); 398 json_decref (pis->address); 399 GNUNET_free (pis); 400 } 401 402 403 struct TALER_TESTING_Command 404 TALER_TESTING_cmd_merchant_post_products2 ( 405 const char *label, 406 const char *merchant_url, 407 const char *product_id, 408 const char *description, 409 json_t *description_i18n, 410 const char *unit, 411 const char *price, 412 const char *image, 413 json_t *taxes, 414 int64_t total_stock, 415 uint32_t minimum_age, 416 json_t *address, 417 struct GNUNET_TIME_Timestamp next_restock, 418 unsigned int http_status) 419 { 420 struct PostProductsState *pis; 421 422 GNUNET_assert ((NULL == taxes) || 423 json_is_array (taxes)); 424 GNUNET_assert ((NULL == description_i18n) || 425 json_is_object (description_i18n)); 426 pis = GNUNET_new (struct PostProductsState); 427 pis->merchant_url = merchant_url; 428 pis->product_id = product_id; 429 pis->http_status = http_status; 430 pis->description = description; 431 pis->description_i18n = description_i18n; /* ownership taken */ 432 pis->unit = unit; 433 pis->unit_precision_level = default_precision_from_unit (unit); 434 GNUNET_assert (GNUNET_OK == 435 TALER_string_to_amount (price, 436 &pis->price)); 437 pis->image = GNUNET_strdup (image); 438 pis->taxes = taxes; /* ownership taken */ 439 pis->total_stock = total_stock; 440 pis->total_stock_frac = 0; 441 pis->unit_allow_fraction = false; 442 pis->use_fractional = false; 443 pis->minimum_age = minimum_age; 444 pis->address = address; /* ownership taken */ 445 pis->next_restock = next_restock; 446 post_products_update_unit_total_stock (pis); 447 { 448 struct TALER_TESTING_Command cmd = { 449 .cls = pis, 450 .label = label, 451 .run = &post_products_run, 452 .cleanup = &post_products_cleanup, 453 .traits = &post_products_traits 454 }; 455 456 return cmd; 457 } 458 } 459 460 461 struct TALER_TESTING_Command 462 TALER_TESTING_cmd_merchant_post_products3 ( 463 const char *label, 464 const char *merchant_url, 465 const char *product_id, 466 const char *description, 467 json_t *description_i18n, 468 const char *unit, 469 const char *price, 470 const char *image, 471 json_t *taxes, 472 int64_t total_stock, 473 uint32_t total_stock_frac, 474 bool unit_allow_fraction, 475 uint32_t minimum_age, 476 json_t *address, 477 struct GNUNET_TIME_Timestamp next_restock, 478 unsigned int http_status) 479 { 480 struct TALER_TESTING_Command cmd; 481 482 cmd = TALER_TESTING_cmd_merchant_post_products2 (label, 483 merchant_url, 484 product_id, 485 description, 486 description_i18n, 487 unit, 488 price, 489 image, 490 taxes, 491 total_stock, 492 minimum_age, 493 address, 494 next_restock, 495 http_status); 496 { 497 struct PostProductsState *pis = cmd.cls; 498 499 pis->total_stock_frac = total_stock_frac; 500 pis->unit_allow_fraction = unit_allow_fraction; 501 pis->use_fractional = true; 502 post_products_update_unit_total_stock (pis); 503 } 504 return cmd; 505 } 506 507 508 struct TALER_TESTING_Command 509 TALER_TESTING_cmd_merchant_post_products ( 510 const char *label, 511 const char *merchant_url, 512 const char *product_id, 513 const char *description, 514 const char *price, 515 unsigned int http_status) 516 { 517 return TALER_TESTING_cmd_merchant_post_products2 ( 518 label, 519 merchant_url, 520 product_id, 521 description, 522 json_pack ("{s:s}", "en", description), 523 "test-unit", 524 price, 525 "", 526 json_array (), 527 4, /* total stock */ 528 0, /* minimum age */ 529 json_pack ("{s:s}", "street", "my street"), 530 GNUNET_TIME_UNIT_ZERO_TS, 531 http_status); 532 } 533 534 535 /* end of testing_api_cmd_post_products.c */