merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

testing_api_cmd_patch_product.c (15416B)


      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 "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    * 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_HttpResponse *hr)
    249 {
    250   struct PatchProductState *pis = cls;
    251 
    252   pis->iph = NULL;
    253   if (pis->http_status != hr->http_status)
    254   {
    255     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    256                 "Unexpected response code %u (%d) to command %s\n",
    257                 hr->http_status,
    258                 (int) hr->ec,
    259                 TALER_TESTING_interpreter_get_current_label (pis->is));
    260     TALER_TESTING_interpreter_fail (pis->is);
    261     return;
    262   }
    263   switch (hr->http_status)
    264   {
    265   case MHD_HTTP_NO_CONTENT:
    266     break;
    267   case MHD_HTTP_UNAUTHORIZED:
    268     break;
    269   case MHD_HTTP_FORBIDDEN:
    270     break;
    271   case MHD_HTTP_NOT_FOUND:
    272     break;
    273   case MHD_HTTP_CONFLICT:
    274     break;
    275   default:
    276     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    277                 "Unhandled HTTP status %u for PATCH /products/ID.\n",
    278                 hr->http_status);
    279   }
    280   TALER_TESTING_interpreter_next (pis->is);
    281 }
    282 
    283 
    284 /**
    285  * Run the "PATCH /products/$ID" CMD.
    286  *
    287  *
    288  * @param cls closure.
    289  * @param cmd command being run now.
    290  * @param is interpreter state.
    291  */
    292 static void
    293 patch_product_run (void *cls,
    294                    const struct TALER_TESTING_Command *cmd,
    295                    struct TALER_TESTING_Interpreter *is)
    296 {
    297   struct PatchProductState *pis = cls;
    298 
    299   pis->is = is;
    300   if (pis->use_fractional)
    301   {
    302     pis->iph = TALER_MERCHANT_product_patch2 (
    303       TALER_TESTING_interpreter_get_context (is),
    304       pis->merchant_url,
    305       pis->product_id,
    306       pis->description,
    307       pis->description_i18n,
    308       pis->unit,
    309       pis->unit_prices,
    310       pis->unit_prices_len,
    311       pis->image,
    312       pis->taxes,
    313       pis->total_stock,
    314       pis->total_stock_frac,
    315       pis->unit_allow_fraction,
    316       pis->use_fractional
    317       ? &pis->unit_precision_level
    318       : NULL,
    319       pis->total_lost,
    320       pis->address,
    321       pis->next_restock,
    322       &patch_product_cb,
    323       pis);
    324   }
    325   else
    326   {
    327     pis->iph = TALER_MERCHANT_product_patch (
    328       TALER_TESTING_interpreter_get_context (is),
    329       pis->merchant_url,
    330       pis->product_id,
    331       pis->description,
    332       pis->description_i18n,
    333       pis->unit,
    334       &pis->price,
    335       pis->image,
    336       pis->taxes,
    337       pis->total_stock,
    338       pis->total_lost,
    339       pis->address,
    340       pis->next_restock,
    341       &patch_product_cb,
    342       pis);
    343   }
    344   GNUNET_assert (NULL != pis->iph);
    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_product_patch_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 */