testing_api_cmd_patch_product.c (12977B)
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 "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 "PATCH /product" CMD. 34 */ 35 struct PatchProductState 36 { 37 38 /** 39 * Handle for a "GET product" request. 40 */ 41 struct TALER_MERCHANT_ProductPatchHandle *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 * 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 * in @e units. 120 */ 121 int64_t total_lost; 122 123 /** 124 * where the product is in stock 125 */ 126 json_t *address; 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 patch_product_update_unit_total_stock (struct PatchProductState *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 PATCH /products/$ID operation. 222 * 223 * @param cls closure for this function 224 * @param hr response being processed 225 */ 226 static void 227 patch_product_cb (void *cls, 228 const struct TALER_MERCHANT_HttpResponse *hr) 229 { 230 struct PatchProductState *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_log (GNUNET_ERROR_TYPE_WARNING, 257 "Unhandled HTTP status %u for PATCH /products/ID.\n", 258 hr->http_status); 259 } 260 TALER_TESTING_interpreter_next (pis->is); 261 } 262 263 264 /** 265 * Run the "PATCH /products/$ID" CMD. 266 * 267 * 268 * @param cls closure. 269 * @param cmd command being run now. 270 * @param is interpreter state. 271 */ 272 static void 273 patch_product_run (void *cls, 274 const struct TALER_TESTING_Command *cmd, 275 struct TALER_TESTING_Interpreter *is) 276 { 277 struct PatchProductState *pis = cls; 278 279 pis->is = is; 280 if (pis->use_fractional) 281 { 282 pis->iph = TALER_MERCHANT_product_patch2 ( 283 TALER_TESTING_interpreter_get_context (is), 284 pis->merchant_url, 285 pis->product_id, 286 pis->description, 287 pis->description_i18n, 288 pis->unit, 289 &pis->price, 290 1, 291 pis->image, 292 pis->taxes, 293 pis->total_stock, 294 pis->total_stock_frac, 295 pis->unit_allow_fraction, 296 pis->use_fractional 297 ? &pis->unit_precision_level 298 : NULL, 299 pis->total_lost, 300 pis->address, 301 pis->next_restock, 302 &patch_product_cb, 303 pis); 304 } 305 else 306 { 307 pis->iph = TALER_MERCHANT_product_patch ( 308 TALER_TESTING_interpreter_get_context (is), 309 pis->merchant_url, 310 pis->product_id, 311 pis->description, 312 pis->description_i18n, 313 pis->unit, 314 &pis->price, 315 pis->image, 316 pis->taxes, 317 pis->total_stock, 318 pis->total_lost, 319 pis->address, 320 pis->next_restock, 321 &patch_product_cb, 322 pis); 323 } 324 GNUNET_assert (NULL != pis->iph); 325 } 326 327 328 /** 329 * Offers information from the PATCH /products CMD state to other 330 * commands. 331 * 332 * @param cls closure 333 * @param[out] ret result (could be anything) 334 * @param trait name of the trait 335 * @param index index number of the object to extract. 336 * @return #GNUNET_OK on success 337 */ 338 static enum GNUNET_GenericReturnValue 339 patch_product_traits (void *cls, 340 const void **ret, 341 const char *trait, 342 unsigned int index) 343 { 344 struct PatchProductState *pps = cls; 345 struct TALER_TESTING_Trait traits[] = { 346 TALER_TESTING_make_trait_product_description (pps->description), 347 TALER_TESTING_make_trait_i18n_description (pps->description_i18n), 348 TALER_TESTING_make_trait_product_unit (pps->unit), 349 TALER_TESTING_make_trait_amount (&pps->price), 350 TALER_TESTING_make_trait_product_image (pps->image), 351 TALER_TESTING_make_trait_taxes (pps->taxes), 352 TALER_TESTING_make_trait_product_stock (&pps->total_stock), 353 TALER_TESTING_make_trait_product_unit_total_stock ( 354 pps->unit_total_stock), 355 TALER_TESTING_make_trait_product_unit_precision_level ( 356 &pps->unit_precision_level), 357 TALER_TESTING_make_trait_product_unit_allow_fraction ( 358 &pps->unit_allow_fraction), 359 TALER_TESTING_make_trait_address (pps->address), 360 TALER_TESTING_make_trait_timestamp (0, 361 &pps->next_restock), 362 TALER_TESTING_make_trait_product_id (pps->product_id), 363 TALER_TESTING_trait_end (), 364 }; 365 366 return TALER_TESTING_get_trait (traits, 367 ret, 368 trait, 369 index); 370 } 371 372 373 /** 374 * Free the state of a "GET product" CMD, and possibly 375 * cancel a pending operation thereof. 376 * 377 * @param cls closure. 378 * @param cmd command being run. 379 */ 380 static void 381 patch_product_cleanup (void *cls, 382 const struct TALER_TESTING_Command *cmd) 383 { 384 struct PatchProductState *pis = cls; 385 386 if (NULL != pis->iph) 387 { 388 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 389 "PATCH /products/$ID operation did not complete\n"); 390 TALER_MERCHANT_product_patch_cancel (pis->iph); 391 } 392 json_decref (pis->description_i18n); 393 GNUNET_free (pis->image); 394 json_decref (pis->taxes); 395 json_decref (pis->address); 396 GNUNET_free (pis); 397 } 398 399 400 struct TALER_TESTING_Command 401 TALER_TESTING_cmd_merchant_patch_product ( 402 const char *label, 403 const char *merchant_url, 404 const char *product_id, 405 const char *description, 406 json_t *description_i18n, 407 const char *unit, 408 const char *price, 409 const char *image, 410 json_t *taxes, 411 int64_t total_stock, 412 uint64_t total_lost, 413 json_t *address, 414 struct GNUNET_TIME_Timestamp next_restock, 415 unsigned int http_status) 416 { 417 struct PatchProductState *pis; 418 419 GNUNET_assert ( (NULL == taxes) || 420 json_is_array (taxes)); 421 pis = GNUNET_new (struct PatchProductState); 422 pis->merchant_url = merchant_url; 423 pis->product_id = product_id; 424 pis->http_status = http_status; 425 pis->description = description; 426 pis->description_i18n = description_i18n; /* ownership taken */ 427 pis->unit = unit; 428 pis->unit_precision_level = default_precision_from_unit (unit); 429 GNUNET_assert (GNUNET_OK == 430 TALER_string_to_amount (price, 431 &pis->price)); 432 pis->image = GNUNET_strdup (image); 433 pis->taxes = taxes; /* ownership taken */ 434 pis->total_stock = total_stock; 435 pis->total_stock_frac = 0; 436 pis->unit_allow_fraction = false; 437 pis->use_fractional = false; 438 pis->total_lost = total_lost; 439 pis->address = address; /* ownership taken */ 440 pis->next_restock = next_restock; 441 patch_product_update_unit_total_stock (pis); 442 { 443 struct TALER_TESTING_Command cmd = { 444 .cls = pis, 445 .label = label, 446 .run = &patch_product_run, 447 .cleanup = &patch_product_cleanup, 448 .traits = &patch_product_traits 449 }; 450 451 return cmd; 452 } 453 } 454 455 456 struct TALER_TESTING_Command 457 TALER_TESTING_cmd_merchant_patch_product2 ( 458 const char *label, 459 const char *merchant_url, 460 const char *product_id, 461 const char *description, 462 json_t *description_i18n, 463 const char *unit, 464 const char *price, 465 const char *image, 466 json_t *taxes, 467 int64_t total_stock, 468 uint32_t total_stock_frac, 469 bool unit_allow_fraction, 470 uint64_t total_lost, 471 json_t *address, 472 struct GNUNET_TIME_Timestamp next_restock, 473 unsigned int http_status) 474 { 475 struct TALER_TESTING_Command cmd; 476 477 cmd = TALER_TESTING_cmd_merchant_patch_product (label, 478 merchant_url, 479 product_id, 480 description, 481 description_i18n, 482 unit, 483 price, 484 image, 485 taxes, 486 total_stock, 487 total_lost, 488 address, 489 next_restock, 490 http_status); 491 { 492 struct PatchProductState *pps = cmd.cls; 493 494 pps->total_stock_frac = total_stock_frac; 495 pps->unit_allow_fraction = unit_allow_fraction; 496 pps->use_fractional = true; 497 patch_product_update_unit_total_stock (pps); 498 } 499 return cmd; 500 } 501 502 503 /* end of testing_api_cmd_patch_product.c */