merchant

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

testing_api_cmd_patch_product.c (17561B)


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