merchant

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

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