merchant

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

testing_api_cmd_post_products.c (18696B)


      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_post_products.c
     21  * @brief command to test POST /products
     22  * @author Christian Grothoff
     23  */
     24 #include "platform.h"
     25 struct PostProductsState;
     26 #define TALER_MERCHANT_POST_PRIVATE_PRODUCTS_RESULT_CLOSURE struct PostProductsState
     27 #include <taler/taler_exchange_service.h>
     28 #include <taler/taler_testing_lib.h>
     29 #include "taler/taler_merchant_testing_lib.h"
     30 #include <taler/merchant/post-private-products.h>
     31 
     32 
     33 /**
     34  * State of a "POST /products" CMD.
     35  */
     36 struct PostProductsState
     37 {
     38 
     39   /**
     40    * Handle for a "POST /products" request.
     41    */
     42   struct TALER_MERCHANT_PostPrivateProductsHandle *iph;
     43 
     44   /**
     45    * The interpreter state.
     46    */
     47   struct TALER_TESTING_Interpreter *is;
     48 
     49   /**
     50    * Base URL of the merchant serving the request.
     51    */
     52   const char *merchant_url;
     53 
     54   /**
     55    * ID of the product to run POST for.
     56    */
     57   const char *product_id;
     58 
     59   /**
     60    * description of the product
     61    */
     62   const char *description;
     63 
     64   /**
     65    * Map from IETF BCP 47 language tags to localized descriptions
     66    */
     67   json_t *description_i18n;
     68 
     69   /**
     70    * unit in which the product is measured (liters, kilograms, packages, etc.)
     71    */
     72   const char *unit;
     73 
     74   /**
     75    * the price for one @a unit of the product
     76    */
     77   struct TALER_Amount price;
     78 
     79   /**
     80    * Array of unit prices (may point to @e price for single-entry usage).
     81    */
     82   struct TALER_Amount *unit_prices;
     83 
     84   /**
     85    * Number of entries in @e unit_prices.
     86    */
     87   size_t unit_prices_len;
     88 
     89   /**
     90    * True if @e unit_prices must be freed.
     91    */
     92   bool owns_unit_prices;
     93 
     94   /**
     95    * True if we should send an explicit unit_price array.
     96    */
     97   bool use_unit_price_array;
     98 
     99   /**
    100    * base64-encoded product image
    101    */
    102   char *image;
    103 
    104   /**
    105    * list of taxes paid by the merchant
    106    */
    107   json_t *taxes;
    108 
    109   /**
    110    * in @e units, -1 to indicate "infinite" (i.e. electronic books)
    111    */
    112   int64_t total_stock;
    113 
    114   /**
    115    * Fractional stock component when fractional quantities are enabled.
    116    */
    117   uint32_t total_stock_frac;
    118 
    119   /**
    120    * Fractional precision level associated with fractional quantities.
    121    */
    122   uint32_t unit_precision_level;
    123 
    124   /**
    125    * whether fractional quantities are allowed for this product.
    126    */
    127   bool unit_allow_fraction;
    128 
    129   /**
    130    * Cached string representation of the stock level.
    131    */
    132   char unit_total_stock[64];
    133 
    134   /**
    135    * set to true if we should use the extended fractional API.
    136    */
    137   bool use_fractional;
    138 
    139   /**
    140    * where the product is in stock
    141    */
    142   json_t *address;
    143 
    144   /**
    145    * Minimum age requirement to use for the product.
    146    */
    147   unsigned int minimum_age;
    148 
    149   /**
    150    * when the next restocking is expected to happen, 0 for unknown,
    151    */
    152   struct GNUNET_TIME_Timestamp next_restock;
    153 
    154   /**
    155    * Categories array length.
    156    */
    157   unsigned int num_cats;
    158 
    159   /**
    160    * Categories array.
    161    */
    162   uint64_t *cats;
    163 
    164   /**
    165    * Expected HTTP response code.
    166    */
    167   unsigned int http_status;
    168 
    169 };
    170 
    171 static void
    172 post_products_update_unit_total_stock (struct PostProductsState *pps)
    173 {
    174   if (-1 == pps->total_stock)
    175   {
    176     pps->total_stock = INT64_MAX;
    177     pps->total_stock_frac = INT32_MAX;
    178   }
    179   else if (! pps->unit_allow_fraction)
    180   {
    181     pps->total_stock_frac = 0;
    182   }
    183   TALER_MERCHANT_format_stock_string ((uint64_t) pps->total_stock,
    184                                       pps->total_stock_frac,
    185                                       pps->unit_total_stock,
    186                                       sizeof (pps->unit_total_stock));
    187 }
    188 
    189 
    190 static uint32_t
    191 default_precision_from_unit (const char *unit)
    192 {
    193   struct PrecisionRule
    194   {
    195     const char *unit;
    196     uint32_t precision;
    197   };
    198   static const struct PrecisionRule rules[] = {
    199     { "WeightUnitMg", 0 },
    200     { "SizeUnitMm", 0 },
    201     { "WeightUnitG", 1 },
    202     { "SizeUnitCm", 1 },
    203     { "SurfaceUnitMm2", 1 },
    204     { "VolumeUnitMm3", 1 },
    205     { "WeightUnitOunce", 2 },
    206     { "SizeUnitInch", 2 },
    207     { "SurfaceUnitCm2", 2 },
    208     { "VolumeUnitOunce", 2 },
    209     { "VolumeUnitInch3", 2 },
    210     { "TimeUnitHour", 2 },
    211     { "TimeUnitMonth", 2 },
    212     { "WeightUnitTon", 3 },
    213     { "WeightUnitKg", 3 },
    214     { "WeightUnitPound", 3 },
    215     { "SizeUnitM", 3 },
    216     { "SizeUnitDm", 3 },
    217     { "SizeUnitFoot", 3 },
    218     { "SurfaceUnitDm2", 3 },
    219     { "SurfaceUnitFoot2", 3 },
    220     { "VolumeUnitCm3", 3 },
    221     { "VolumeUnitLitre", 3 },
    222     { "VolumeUnitGallon", 3 },
    223     { "TimeUnitSecond", 3 },
    224     { "TimeUnitMinute", 3 },
    225     { "TimeUnitDay", 3 },
    226     { "TimeUnitWeek", 3 },
    227     { "SurfaceUnitM2", 4 },
    228     { "SurfaceUnitInch2", 4 },
    229     { "TimeUnitYear", 4 },
    230     { "VolumeUnitDm3", 5 },
    231     { "VolumeUnitFoot3", 5 },
    232     { "VolumeUnitM3", 6 }
    233   };
    234 
    235   const size_t rules_len = sizeof (rules) / sizeof (rules[0]);
    236   if (NULL == unit)
    237     return 0;
    238 
    239   for (size_t i = 0; i<rules_len; i++)
    240     if (0 == strcmp (unit,
    241                      rules[i].unit))
    242       return rules[i].precision;
    243   return 0;
    244 }
    245 
    246 
    247 /**
    248  * Callback for a POST /products operation.
    249  *
    250  * @param cls closure for this function
    251  * @param ppr response being processed
    252  */
    253 static void
    254 post_products_cb (struct PostProductsState *pis,
    255                   const struct TALER_MERCHANT_PostPrivateProductsResponse *ppr)
    256 {
    257 
    258   pis->iph = NULL;
    259   if (pis->http_status != ppr->hr.http_status)
    260   {
    261     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    262                 "Unexpected response code %u (%d) to command %s\n",
    263                 ppr->hr.http_status,
    264                 (int) ppr->hr.ec,
    265                 TALER_TESTING_interpreter_get_current_label (pis->is));
    266     TALER_TESTING_interpreter_fail (pis->is);
    267     return;
    268   }
    269   switch (ppr->hr.http_status)
    270   {
    271   case MHD_HTTP_NO_CONTENT:
    272     break;
    273   case MHD_HTTP_BAD_REQUEST:
    274     break;
    275   case MHD_HTTP_UNAUTHORIZED:
    276     break;
    277   case MHD_HTTP_FORBIDDEN:
    278     break;
    279   case MHD_HTTP_NOT_FOUND:
    280     break;
    281   case MHD_HTTP_CONFLICT:
    282     break;
    283   default:
    284     GNUNET_break (0);
    285     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    286                 "Unhandled HTTP status %u for POST /products.\n",
    287                 ppr->hr.http_status);
    288   }
    289   TALER_TESTING_interpreter_next (pis->is);
    290 }
    291 
    292 
    293 /**
    294  * Run the "POST /products" CMD.
    295  *
    296  *
    297  * @param cls closure.
    298  * @param cmd command being run now.
    299  * @param is interpreter state.
    300  */
    301 static void
    302 post_products_run (void *cls,
    303                    const struct TALER_TESTING_Command *cmd,
    304                    struct TALER_TESTING_Interpreter *is)
    305 {
    306   struct PostProductsState *pis = cls;
    307 
    308   pis->is = is;
    309   pis->iph = TALER_MERCHANT_post_private_products_create (
    310     TALER_TESTING_interpreter_get_context (is),
    311     pis->merchant_url,
    312     pis->product_id,
    313     pis->description,
    314     pis->unit,
    315     pis->unit_prices_len,
    316     pis->unit_prices);
    317   GNUNET_assert (GNUNET_OK ==
    318                  TALER_MERCHANT_post_private_products_set_options (
    319                    pis->iph,
    320                    TALER_MERCHANT_post_private_products_option_description_i18n (
    321                      pis->description_i18n),
    322                    TALER_MERCHANT_post_private_products_option_total_stock_val (
    323                      pis->total_stock),
    324                    TALER_MERCHANT_post_private_products_option_taxes (
    325                      pis->taxes),
    326                    TALER_MERCHANT_post_private_products_option_image (
    327                      pis->image),
    328                    TALER_MERCHANT_post_private_products_option_address (
    329                      pis->address),
    330                    TALER_MERCHANT_post_private_products_option_next_restock (
    331                      pis->next_restock),
    332                    TALER_MERCHANT_post_private_products_option_minimum_age (
    333                      pis->minimum_age),
    334                    TALER_MERCHANT_post_private_products_option_categories (
    335                      pis->num_cats,
    336                      pis->cats)));
    337   if (pis->use_fractional)
    338   {
    339     GNUNET_assert (
    340       GNUNET_OK ==
    341       TALER_MERCHANT_post_private_products_set_options (
    342         pis->iph,
    343         TALER_MERCHANT_post_private_products_option_total_stock_frac (
    344           pis->total_stock_frac),
    345         TALER_MERCHANT_post_private_products_option_unit_allow_fraction (
    346           pis->unit_allow_fraction),
    347         TALER_MERCHANT_post_private_products_option_unit_precision_level (
    348           pis->unit_precision_level)));
    349   }
    350   {
    351     enum TALER_ErrorCode ec;
    352 
    353     ec = TALER_MERCHANT_post_private_products_start (
    354       pis->iph,
    355       &post_products_cb,
    356       pis);
    357     GNUNET_assert (TALER_EC_NONE == ec);
    358   }
    359 }
    360 
    361 
    362 /**
    363  * Offers information from the POST /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 post_products_traits (void *cls,
    374                       const void **ret,
    375                       const char *trait,
    376                       unsigned int index)
    377 {
    378   struct PostProductsState *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 "POST 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 post_products_cleanup (void *cls,
    416                        const struct TALER_TESTING_Command *cmd)
    417 {
    418   struct PostProductsState *pis = cls;
    419 
    420   if (NULL != pis->iph)
    421   {
    422     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    423                 "POST /products operation did not complete\n");
    424     TALER_MERCHANT_post_private_products_cancel (pis->iph);
    425   }
    426   if (pis->owns_unit_prices)
    427     GNUNET_free (pis->unit_prices);
    428   json_decref (pis->description_i18n);
    429   GNUNET_free (pis->image);
    430   json_decref (pis->taxes);
    431   json_decref (pis->address);
    432   GNUNET_free (pis->cats);
    433   GNUNET_free (pis);
    434 }
    435 
    436 
    437 struct TALER_TESTING_Command
    438 TALER_TESTING_cmd_merchant_post_products2 (
    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   uint32_t minimum_age,
    450   json_t *address,
    451   struct GNUNET_TIME_Timestamp next_restock,
    452   unsigned int http_status)
    453 {
    454   struct PostProductsState *pis;
    455 
    456   GNUNET_assert ((NULL == taxes) ||
    457                  json_is_array (taxes));
    458   GNUNET_assert ((NULL == description_i18n) ||
    459                  json_is_object (description_i18n));
    460   pis = GNUNET_new (struct PostProductsState);
    461   pis->merchant_url = merchant_url;
    462   pis->product_id = product_id;
    463   pis->http_status = http_status;
    464   pis->description = description;
    465   pis->description_i18n = description_i18n; /* ownership taken */
    466   pis->unit = unit;
    467   pis->unit_precision_level = default_precision_from_unit (unit);
    468   GNUNET_assert (GNUNET_OK ==
    469                  TALER_string_to_amount (price,
    470                                          &pis->price));
    471   pis->unit_prices = &pis->price;
    472   pis->unit_prices_len = 1;
    473   pis->owns_unit_prices = false;
    474   pis->use_unit_price_array = false;
    475   pis->image = GNUNET_strdup (image);
    476   pis->taxes = taxes; /* ownership taken */
    477   pis->total_stock = total_stock;
    478   pis->total_stock_frac = 0;
    479   pis->unit_allow_fraction = false;
    480   pis->use_fractional = false;
    481   pis->minimum_age = minimum_age;
    482   pis->address = address; /* ownership taken */
    483   pis->next_restock = next_restock;
    484   pis->num_cats = 0;
    485   pis->cats = NULL;
    486   post_products_update_unit_total_stock (pis);
    487   {
    488     struct TALER_TESTING_Command cmd = {
    489       .cls = pis,
    490       .label = label,
    491       .run = &post_products_run,
    492       .cleanup = &post_products_cleanup,
    493       .traits = &post_products_traits
    494     };
    495 
    496     return cmd;
    497   }
    498 }
    499 
    500 
    501 struct TALER_TESTING_Command
    502 TALER_TESTING_cmd_merchant_post_products3 (
    503   const char *label,
    504   const char *merchant_url,
    505   const char *product_id,
    506   const char *description,
    507   json_t *description_i18n,
    508   const char *unit,
    509   const char *price,
    510   const char *image,
    511   json_t *taxes,
    512   int64_t total_stock,
    513   uint32_t total_stock_frac,
    514   bool unit_allow_fraction,
    515   uint32_t minimum_age,
    516   json_t *address,
    517   struct GNUNET_TIME_Timestamp next_restock,
    518   unsigned int http_status)
    519 {
    520   struct TALER_TESTING_Command cmd;
    521 
    522   cmd = TALER_TESTING_cmd_merchant_post_products2 (label,
    523                                                    merchant_url,
    524                                                    product_id,
    525                                                    description,
    526                                                    description_i18n,
    527                                                    unit,
    528                                                    price,
    529                                                    image,
    530                                                    taxes,
    531                                                    total_stock,
    532                                                    minimum_age,
    533                                                    address,
    534                                                    next_restock,
    535                                                    http_status);
    536   {
    537     struct PostProductsState *pis = cmd.cls;
    538 
    539     pis->total_stock_frac = total_stock_frac;
    540     pis->unit_allow_fraction = unit_allow_fraction;
    541     pis->use_fractional = true;
    542     post_products_update_unit_total_stock (pis);
    543   }
    544   return cmd;
    545 }
    546 
    547 
    548 struct TALER_TESTING_Command
    549 TALER_TESTING_cmd_merchant_post_products_with_unit_prices (
    550   const char *label,
    551   const char *merchant_url,
    552   const char *product_id,
    553   const char *description,
    554   const char *unit,
    555   const char *const *unit_prices,
    556   size_t unit_prices_len,
    557   unsigned int http_status)
    558 {
    559   struct PostProductsState *pis;
    560 
    561   GNUNET_assert (0 < unit_prices_len);
    562   GNUNET_assert (NULL != unit_prices);
    563   pis = GNUNET_new (struct PostProductsState);
    564   pis->merchant_url = merchant_url;
    565   pis->product_id = product_id;
    566   pis->http_status = http_status;
    567   pis->description = description;
    568   pis->description_i18n = json_pack ("{s:s}", "en", description);
    569   pis->unit = unit;
    570   pis->unit_precision_level = default_precision_from_unit (unit);
    571   pis->unit_prices = GNUNET_new_array (unit_prices_len,
    572                                        struct TALER_Amount);
    573   pis->unit_prices_len = unit_prices_len;
    574   pis->owns_unit_prices = true;
    575   pis->use_unit_price_array = true;
    576   for (size_t i = 0; i < unit_prices_len; i++)
    577     GNUNET_assert (GNUNET_OK ==
    578                    TALER_string_to_amount (unit_prices[i],
    579                                            &pis->unit_prices[i]));
    580   pis->price = pis->unit_prices[0];
    581   pis->image = GNUNET_strdup ("");
    582   pis->taxes = json_array ();
    583   pis->total_stock = 4;
    584   pis->total_stock_frac = 0;
    585   pis->unit_allow_fraction = false;
    586   pis->use_fractional = false;
    587   pis->minimum_age = 0;
    588   pis->address = json_pack ("{s:s}", "street", "my street");
    589   pis->next_restock = GNUNET_TIME_UNIT_ZERO_TS;
    590   pis->num_cats = 0;
    591   pis->cats = NULL;
    592   post_products_update_unit_total_stock (pis);
    593   {
    594     struct TALER_TESTING_Command cmd = {
    595       .cls = pis,
    596       .label = label,
    597       .run = &post_products_run,
    598       .cleanup = &post_products_cleanup,
    599       .traits = &post_products_traits
    600     };
    601 
    602     return cmd;
    603   }
    604 }
    605 
    606 
    607 struct TALER_TESTING_Command
    608 TALER_TESTING_cmd_merchant_post_products (
    609   const char *label,
    610   const char *merchant_url,
    611   const char *product_id,
    612   const char *description,
    613   const char *price,
    614   unsigned int http_status)
    615 {
    616   return TALER_TESTING_cmd_merchant_post_products2 (
    617     label,
    618     merchant_url,
    619     product_id,
    620     description,
    621     json_pack ("{s:s}", "en", description),
    622     "test-unit",
    623     price,
    624     "",
    625     json_array (),
    626     4, /* total stock */
    627     0, /* minimum age */
    628     json_pack ("{s:s}", "street", "my street"),
    629     GNUNET_TIME_UNIT_ZERO_TS,
    630     http_status);
    631 }
    632 
    633 
    634 struct TALER_TESTING_Command
    635 TALER_TESTING_cmd_merchant_post_products_with_categories (
    636   const char *label,
    637   const char *merchant_url,
    638   const char *product_id,
    639   const char *description,
    640   const char *unit,
    641   const char *price,
    642   unsigned int num_cats,
    643   const uint64_t *cats,
    644   bool unit_allow_fraction,
    645   uint32_t unit_precision_level,
    646   unsigned int http_status)
    647 {
    648   struct TALER_TESTING_Command cmd;
    649 
    650   cmd = TALER_TESTING_cmd_merchant_post_products2 (
    651     label,
    652     merchant_url,
    653     product_id,
    654     description,
    655     json_pack ("{s:s}", "en", description),
    656     unit,
    657     price,
    658     "",
    659     json_array (),
    660     4, /* total stock */
    661     0, /* minimum age */
    662     json_pack ("{s:s}", "street", "my street"),
    663     GNUNET_TIME_UNIT_ZERO_TS,
    664     http_status);
    665   {
    666     struct PostProductsState *pis = cmd.cls;
    667 
    668     pis->num_cats = num_cats;
    669     if (0 < num_cats)
    670     {
    671       pis->cats = GNUNET_new_array (num_cats,
    672                                     uint64_t);
    673       for (unsigned int i = 0; i < num_cats; i++)
    674         pis->cats[i] = cats[i];
    675     }
    676     pis->unit_allow_fraction = unit_allow_fraction;
    677     pis->unit_precision_level = unit_precision_level;
    678     if (unit_allow_fraction)
    679       pis->use_fractional = true;
    680   }
    681   return cmd;
    682 }
    683 
    684 
    685 /* end of testing_api_cmd_post_products.c */