merchant

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

testing_api_cmd_post_products.c (18656B)


      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_UNAUTHORIZED:
    274     break;
    275   case MHD_HTTP_FORBIDDEN:
    276     break;
    277   case MHD_HTTP_NOT_FOUND:
    278     break;
    279   case MHD_HTTP_CONFLICT:
    280     break;
    281   default:
    282     GNUNET_break (0);
    283     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    284                 "Unhandled HTTP status %u for POST /products.\n",
    285                 ppr->hr.http_status);
    286   }
    287   TALER_TESTING_interpreter_next (pis->is);
    288 }
    289 
    290 
    291 /**
    292  * Run the "POST /products" CMD.
    293  *
    294  *
    295  * @param cls closure.
    296  * @param cmd command being run now.
    297  * @param is interpreter state.
    298  */
    299 static void
    300 post_products_run (void *cls,
    301                    const struct TALER_TESTING_Command *cmd,
    302                    struct TALER_TESTING_Interpreter *is)
    303 {
    304   struct PostProductsState *pis = cls;
    305 
    306   pis->is = is;
    307   pis->iph = TALER_MERCHANT_post_private_products_create (
    308     TALER_TESTING_interpreter_get_context (is),
    309     pis->merchant_url,
    310     pis->product_id,
    311     pis->description,
    312     pis->unit,
    313     pis->unit_prices_len,
    314     pis->unit_prices);
    315   GNUNET_assert (GNUNET_OK ==
    316                  TALER_MERCHANT_post_private_products_set_options (
    317                    pis->iph,
    318                    TALER_MERCHANT_post_private_products_option_description_i18n (
    319                      pis->description_i18n),
    320                    TALER_MERCHANT_post_private_products_option_total_stock_val (
    321                      pis->total_stock),
    322                    TALER_MERCHANT_post_private_products_option_taxes (
    323                      pis->taxes),
    324                    TALER_MERCHANT_post_private_products_option_image (
    325                      pis->image),
    326                    TALER_MERCHANT_post_private_products_option_address (
    327                      pis->address),
    328                    TALER_MERCHANT_post_private_products_option_next_restock (
    329                      pis->next_restock),
    330                    TALER_MERCHANT_post_private_products_option_minimum_age (
    331                      pis->minimum_age),
    332                    TALER_MERCHANT_post_private_products_option_categories (
    333                      pis->num_cats,
    334                      pis->cats)));
    335   if (pis->use_fractional)
    336   {
    337     GNUNET_assert (
    338       GNUNET_OK ==
    339       TALER_MERCHANT_post_private_products_set_options (
    340         pis->iph,
    341         TALER_MERCHANT_post_private_products_option_total_stock_frac (
    342           pis->total_stock_frac),
    343         TALER_MERCHANT_post_private_products_option_unit_allow_fraction (
    344           pis->unit_allow_fraction),
    345         TALER_MERCHANT_post_private_products_option_unit_precision_level (
    346           pis->unit_precision_level)));
    347   }
    348   {
    349     enum TALER_ErrorCode ec;
    350 
    351     ec = TALER_MERCHANT_post_private_products_start (
    352       pis->iph,
    353       &post_products_cb,
    354       pis);
    355     GNUNET_assert (TALER_EC_NONE == ec);
    356   }
    357 }
    358 
    359 
    360 /**
    361  * Offers information from the POST /products CMD state to other
    362  * commands.
    363  *
    364  * @param cls closure
    365  * @param[out] ret result (could be anything)
    366  * @param trait name of the trait
    367  * @param index index number of the object to extract.
    368  * @return #GNUNET_OK on success
    369  */
    370 static enum GNUNET_GenericReturnValue
    371 post_products_traits (void *cls,
    372                       const void **ret,
    373                       const char *trait,
    374                       unsigned int index)
    375 {
    376   struct PostProductsState *pps = cls;
    377   struct TALER_TESTING_Trait traits[] = {
    378     TALER_TESTING_make_trait_product_description (pps->description),
    379     TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
    380     TALER_TESTING_make_trait_product_unit (pps->unit),
    381     TALER_TESTING_make_trait_amount (&pps->price),
    382     TALER_TESTING_make_trait_product_image (pps->image),
    383     TALER_TESTING_make_trait_taxes (pps->taxes),
    384     TALER_TESTING_make_trait_product_stock (&pps->total_stock),
    385     TALER_TESTING_make_trait_product_unit_total_stock (
    386       pps->unit_total_stock),
    387     TALER_TESTING_make_trait_product_unit_precision_level (
    388       &pps->unit_precision_level),
    389     TALER_TESTING_make_trait_product_unit_allow_fraction (
    390       &pps->unit_allow_fraction),
    391     TALER_TESTING_make_trait_address (pps->address),
    392     TALER_TESTING_make_trait_timestamp (0,
    393                                         &pps->next_restock),
    394     TALER_TESTING_make_trait_product_id (pps->product_id),
    395     TALER_TESTING_trait_end (),
    396   };
    397 
    398   return TALER_TESTING_get_trait (traits,
    399                                   ret,
    400                                   trait,
    401                                   index);
    402 }
    403 
    404 
    405 /**
    406  * Free the state of a "POST product" CMD, and possibly
    407  * cancel a pending operation thereof.
    408  *
    409  * @param cls closure.
    410  * @param cmd command being run.
    411  */
    412 static void
    413 post_products_cleanup (void *cls,
    414                        const struct TALER_TESTING_Command *cmd)
    415 {
    416   struct PostProductsState *pis = cls;
    417 
    418   if (NULL != pis->iph)
    419   {
    420     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    421                 "POST /products operation did not complete\n");
    422     TALER_MERCHANT_post_private_products_cancel (pis->iph);
    423   }
    424   if (pis->owns_unit_prices)
    425     GNUNET_free (pis->unit_prices);
    426   json_decref (pis->description_i18n);
    427   GNUNET_free (pis->image);
    428   json_decref (pis->taxes);
    429   json_decref (pis->address);
    430   GNUNET_free (pis->cats);
    431   GNUNET_free (pis);
    432 }
    433 
    434 
    435 struct TALER_TESTING_Command
    436 TALER_TESTING_cmd_merchant_post_products2 (
    437   const char *label,
    438   const char *merchant_url,
    439   const char *product_id,
    440   const char *description,
    441   json_t *description_i18n,
    442   const char *unit,
    443   const char *price,
    444   const char *image,
    445   json_t *taxes,
    446   int64_t total_stock,
    447   uint32_t minimum_age,
    448   json_t *address,
    449   struct GNUNET_TIME_Timestamp next_restock,
    450   unsigned int http_status)
    451 {
    452   struct PostProductsState *pis;
    453 
    454   GNUNET_assert ((NULL == taxes) ||
    455                  json_is_array (taxes));
    456   GNUNET_assert ((NULL == description_i18n) ||
    457                  json_is_object (description_i18n));
    458   pis = GNUNET_new (struct PostProductsState);
    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->minimum_age = minimum_age;
    480   pis->address = address; /* ownership taken */
    481   pis->next_restock = next_restock;
    482   pis->num_cats = 0;
    483   pis->cats = NULL;
    484   post_products_update_unit_total_stock (pis);
    485   {
    486     struct TALER_TESTING_Command cmd = {
    487       .cls = pis,
    488       .label = label,
    489       .run = &post_products_run,
    490       .cleanup = &post_products_cleanup,
    491       .traits = &post_products_traits
    492     };
    493 
    494     return cmd;
    495   }
    496 }
    497 
    498 
    499 struct TALER_TESTING_Command
    500 TALER_TESTING_cmd_merchant_post_products3 (
    501   const char *label,
    502   const char *merchant_url,
    503   const char *product_id,
    504   const char *description,
    505   json_t *description_i18n,
    506   const char *unit,
    507   const char *price,
    508   const char *image,
    509   json_t *taxes,
    510   int64_t total_stock,
    511   uint32_t total_stock_frac,
    512   bool unit_allow_fraction,
    513   uint32_t minimum_age,
    514   json_t *address,
    515   struct GNUNET_TIME_Timestamp next_restock,
    516   unsigned int http_status)
    517 {
    518   struct TALER_TESTING_Command cmd;
    519 
    520   cmd = TALER_TESTING_cmd_merchant_post_products2 (label,
    521                                                    merchant_url,
    522                                                    product_id,
    523                                                    description,
    524                                                    description_i18n,
    525                                                    unit,
    526                                                    price,
    527                                                    image,
    528                                                    taxes,
    529                                                    total_stock,
    530                                                    minimum_age,
    531                                                    address,
    532                                                    next_restock,
    533                                                    http_status);
    534   {
    535     struct PostProductsState *pis = cmd.cls;
    536 
    537     pis->total_stock_frac = total_stock_frac;
    538     pis->unit_allow_fraction = unit_allow_fraction;
    539     pis->use_fractional = true;
    540     post_products_update_unit_total_stock (pis);
    541   }
    542   return cmd;
    543 }
    544 
    545 
    546 struct TALER_TESTING_Command
    547 TALER_TESTING_cmd_merchant_post_products_with_unit_prices (
    548   const char *label,
    549   const char *merchant_url,
    550   const char *product_id,
    551   const char *description,
    552   const char *unit,
    553   const char *const *unit_prices,
    554   size_t unit_prices_len,
    555   unsigned int http_status)
    556 {
    557   struct PostProductsState *pis;
    558 
    559   GNUNET_assert (0 < unit_prices_len);
    560   GNUNET_assert (NULL != unit_prices);
    561   pis = GNUNET_new (struct PostProductsState);
    562   pis->merchant_url = merchant_url;
    563   pis->product_id = product_id;
    564   pis->http_status = http_status;
    565   pis->description = description;
    566   pis->description_i18n = json_pack ("{s:s}", "en", description);
    567   pis->unit = unit;
    568   pis->unit_precision_level = default_precision_from_unit (unit);
    569   pis->unit_prices = GNUNET_new_array (unit_prices_len,
    570                                        struct TALER_Amount);
    571   pis->unit_prices_len = unit_prices_len;
    572   pis->owns_unit_prices = true;
    573   pis->use_unit_price_array = true;
    574   for (size_t i = 0; i < unit_prices_len; i++)
    575     GNUNET_assert (GNUNET_OK ==
    576                    TALER_string_to_amount (unit_prices[i],
    577                                            &pis->unit_prices[i]));
    578   pis->price = pis->unit_prices[0];
    579   pis->image = GNUNET_strdup ("");
    580   pis->taxes = json_array ();
    581   pis->total_stock = 4;
    582   pis->total_stock_frac = 0;
    583   pis->unit_allow_fraction = false;
    584   pis->use_fractional = false;
    585   pis->minimum_age = 0;
    586   pis->address = json_pack ("{s:s}", "street", "my street");
    587   pis->next_restock = GNUNET_TIME_UNIT_ZERO_TS;
    588   pis->num_cats = 0;
    589   pis->cats = NULL;
    590   post_products_update_unit_total_stock (pis);
    591   {
    592     struct TALER_TESTING_Command cmd = {
    593       .cls = pis,
    594       .label = label,
    595       .run = &post_products_run,
    596       .cleanup = &post_products_cleanup,
    597       .traits = &post_products_traits
    598     };
    599 
    600     return cmd;
    601   }
    602 }
    603 
    604 
    605 struct TALER_TESTING_Command
    606 TALER_TESTING_cmd_merchant_post_products (
    607   const char *label,
    608   const char *merchant_url,
    609   const char *product_id,
    610   const char *description,
    611   const char *price,
    612   unsigned int http_status)
    613 {
    614   return TALER_TESTING_cmd_merchant_post_products2 (
    615     label,
    616     merchant_url,
    617     product_id,
    618     description,
    619     json_pack ("{s:s}", "en", description),
    620     "test-unit",
    621     price,
    622     "",
    623     json_array (),
    624     4, /* total stock */
    625     0, /* minimum age */
    626     json_pack ("{s:s}", "street", "my street"),
    627     GNUNET_TIME_UNIT_ZERO_TS,
    628     http_status);
    629 }
    630 
    631 
    632 struct TALER_TESTING_Command
    633 TALER_TESTING_cmd_merchant_post_products_with_categories (
    634   const char *label,
    635   const char *merchant_url,
    636   const char *product_id,
    637   const char *description,
    638   const char *unit,
    639   const char *price,
    640   unsigned int num_cats,
    641   const uint64_t *cats,
    642   bool unit_allow_fraction,
    643   uint32_t unit_precision_level,
    644   unsigned int http_status)
    645 {
    646   struct TALER_TESTING_Command cmd;
    647 
    648   cmd = TALER_TESTING_cmd_merchant_post_products2 (
    649     label,
    650     merchant_url,
    651     product_id,
    652     description,
    653     json_pack ("{s:s}", "en", description),
    654     unit,
    655     price,
    656     "",
    657     json_array (),
    658     4, /* total stock */
    659     0, /* minimum age */
    660     json_pack ("{s:s}", "street", "my street"),
    661     GNUNET_TIME_UNIT_ZERO_TS,
    662     http_status);
    663   {
    664     struct PostProductsState *pis = cmd.cls;
    665 
    666     pis->num_cats = num_cats;
    667     if (0 < num_cats)
    668     {
    669       pis->cats = GNUNET_new_array (num_cats,
    670                                     uint64_t);
    671       for (unsigned int i = 0; i < num_cats; i++)
    672         pis->cats[i] = cats[i];
    673     }
    674     pis->unit_allow_fraction = unit_allow_fraction;
    675     pis->unit_precision_level = unit_precision_level;
    676     if (unit_allow_fraction)
    677       pis->use_fractional = true;
    678   }
    679   return cmd;
    680 }
    681 
    682 
    683 /* end of testing_api_cmd_post_products.c */