merchant

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

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 */