merchant

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

testing_api_cmd_wallet_get_template.c (16472B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file src/testing/testing_api_cmd_wallet_get_template.c
     18  * @brief command to test GET /templates/$ID (wallet)
     19  * @author Bohdan Potuzhnyi
     20  */
     21 #include "platform.h"
     22 struct WalletGetTemplateState;
     23 #define TALER_MERCHANT_GET_TEMPLATES_RESULT_CLOSURE struct WalletGetTemplateState
     24 #include <microhttpd.h>
     25 #include <jansson.h>
     26 #include <taler/taler_testing_lib.h>
     27 #include "taler/taler_merchant_service.h"
     28 #include "taler/taler_merchant_testing_lib.h"
     29 #include <taler/merchant/get-templates-TEMPLATE_ID.h>
     30 
     31 /**
     32  * State of a "GET template" wallet CMD.
     33  */
     34 struct WalletGetTemplateState
     35 {
     36   /**
     37    * Handle for a "GET template" request.
     38    */
     39   struct TALER_MERCHANT_GetTemplatesHandle *igh;
     40 
     41   /**
     42    * The interpreter state.
     43    */
     44   struct TALER_TESTING_Interpreter *is;
     45 
     46   /**
     47    * Base URL of the merchant serving the request.
     48    */
     49   const char *merchant_url;
     50 
     51   /**
     52    * ID of the template to run GET for.
     53    */
     54   const char *template_id;
     55 
     56   /**
     57    * Expected product count (0 to ignore).
     58    */
     59   size_t expected_products_len;
     60 
     61   /**
     62    * Product id to verify unit info for (optional).
     63    */
     64   const char *expected_product_id;
     65 
     66   /**
     67    * Expected unit for @e expected_product_id.
     68    */
     69   const char *expected_unit;
     70 
     71   /**
     72    * Expected allow_fraction for @e expected_product_id.
     73    */
     74   bool expected_unit_allow_fraction;
     75 
     76   /**
     77    * Expected precision for @e expected_product_id.
     78    */
     79   uint32_t expected_unit_precision_level;
     80 
     81   /**
     82    * Expected unit name short i18n for @e expected_unit.
     83    */
     84   json_t *expected_unit_name_short_i18n;
     85 
     86   /**
     87    * Optional second product id expected to be present.
     88    */
     89   const char *expected_product_id2;
     90 
     91   /**
     92    * Optional third product id expected to be present.
     93    */
     94   const char *expected_product_id3;
     95 
     96   /**
     97    * Expected category id 1 (0 to ignore).
     98    */
     99   uint64_t expected_category_id1;
    100 
    101   /**
    102    * Expected category id 2 (0 to ignore).
    103    */
    104   uint64_t expected_category_id2;
    105 
    106   /**
    107    * Exchange candidate URL expected in the response (optional).
    108    */
    109   const char *expected_exchange_url;
    110 
    111   /**
    112    * Wire method expected for @e expected_exchange_url.
    113    */
    114   const char *expected_wire_method;
    115 
    116   /**
    117    * Expected HTTP response code.
    118    */
    119   unsigned int http_status;
    120 };
    121 
    122 static bool
    123 product_id_matches (const json_t *product,
    124                     const char *expected_id)
    125 {
    126   const json_t *id_val;
    127 
    128   if (NULL == expected_id)
    129     return false;
    130   id_val = json_object_get (product,
    131                             "product_id");
    132   if (! json_is_string (id_val))
    133     return false;
    134   return (0 == strcmp (json_string_value (id_val),
    135                        expected_id));
    136 }
    137 
    138 
    139 /**
    140  * Callback for a wallet /get/templates/$ID operation.
    141  *
    142  * @param cls closure for this function
    143  * @param tgr HTTP response details
    144  */
    145 static void
    146 wallet_get_template_cb (struct WalletGetTemplateState *wgs,
    147                         const struct
    148                         TALER_MERCHANT_GetTemplatesResponse *tgr)
    149 {
    150 
    151   wgs->igh = NULL;
    152   if (wgs->http_status != tgr->hr.http_status)
    153   {
    154     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    155                 "Unexpected response code %u (%d) to command %s\n",
    156                 tgr->hr.http_status,
    157                 (int) tgr->hr.ec,
    158                 TALER_TESTING_interpreter_get_current_label (wgs->is));
    159     TALER_TESTING_interpreter_fail (wgs->is);
    160     return;
    161   }
    162   if (MHD_HTTP_OK == tgr->hr.http_status)
    163   {
    164     const json_t *template_contract = tgr->details.ok.template_contract;
    165     const json_t *inventory_payload;
    166     const json_t *products;
    167     bool expect_inventory;
    168 
    169     if ( (NULL == tgr->details.ok.merchant) ||
    170          (NULL == tgr->details.ok.merchant_pub) )
    171     {
    172       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    173                   "Missing merchant identity in wallet template\n");
    174       TALER_TESTING_interpreter_fail (wgs->is);
    175       return;
    176     }
    177     if (NULL != wgs->expected_exchange_url)
    178     {
    179       bool found_candidate = false;
    180 
    181       if (! tgr->details.ok.exchange_candidates_provided)
    182       {
    183         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    184                     "Missing exchange_candidates in wallet template\n");
    185         TALER_TESTING_interpreter_fail (wgs->is);
    186         return;
    187       }
    188       for (unsigned int i = 0;
    189            i<tgr->details.ok.num_exchange_candidates;
    190            i++)
    191       {
    192         const struct TALER_MERCHANT_TemplateExchangeCandidate *candidate
    193           = &tgr->details.ok.exchange_candidates[i];
    194         bool found_method = (NULL == wgs->expected_wire_method);
    195 
    196         if (0 != strcmp (candidate->base_url,
    197                          wgs->expected_exchange_url))
    198           continue;
    199         if (NULL != wgs->expected_wire_method)
    200           for (unsigned int j = 0; j<candidate->num_wire_methods; j++)
    201             if (0 == strcasecmp (candidate->wire_methods[j],
    202                                  wgs->expected_wire_method))
    203               found_method = true;
    204         if (found_method)
    205         {
    206           found_candidate = true;
    207           break;
    208         }
    209       }
    210       if (! found_candidate)
    211       {
    212         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    213                     "Expected compatible exchange candidate missing\n");
    214         TALER_TESTING_interpreter_fail (wgs->is);
    215         return;
    216       }
    217     }
    218 
    219     inventory_payload = json_object_get (template_contract,
    220                                          "inventory_payload");
    221     expect_inventory = ( (0 < wgs->expected_products_len) ||
    222                          (NULL != wgs->expected_product_id) ||
    223                          (NULL != wgs->expected_product_id2) ||
    224                          (NULL != wgs->expected_product_id3) ||
    225                          (NULL != wgs->expected_unit_name_short_i18n) ||
    226                          (0 != wgs->expected_category_id1) ||
    227                          (0 != wgs->expected_category_id2) );
    228     if (! json_is_object (inventory_payload))
    229     {
    230       if (! expect_inventory)
    231         goto done;
    232       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    233                   "Missing inventory_payload in wallet template\n");
    234       TALER_TESTING_interpreter_fail (wgs->is);
    235       return;
    236     }
    237     products = json_object_get (inventory_payload,
    238                                 "products");
    239     if (! json_is_array (products))
    240     {
    241       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    242                   "Missing products in wallet template\n");
    243       TALER_TESTING_interpreter_fail (wgs->is);
    244       return;
    245     }
    246     if ( (0 < wgs->expected_products_len) &&
    247          (json_array_size (products) != wgs->expected_products_len) )
    248     {
    249       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    250                   "Unexpected products length\n");
    251       TALER_TESTING_interpreter_fail (wgs->is);
    252       return;
    253     }
    254 
    255     {
    256       bool found_unit = (NULL == wgs->expected_product_id);
    257       bool found_extra = (NULL == wgs->expected_product_id2);
    258       bool found_extra2 = (NULL == wgs->expected_product_id3);
    259 
    260       for (size_t i = 0; i < json_array_size (products); i++)
    261       {
    262         const json_t *product = json_array_get (products,
    263                                                 i);
    264 
    265         if (product_id_matches (product,
    266                                 wgs->expected_product_id))
    267         {
    268           const json_t *unit_val;
    269           const json_t *allow_val;
    270           const json_t *prec_val;
    271 
    272           unit_val = json_object_get (product,
    273                                       "unit");
    274           allow_val = json_object_get (product,
    275                                        "unit_allow_fraction");
    276           prec_val = json_object_get (product,
    277                                       "unit_precision_level");
    278           if ( (NULL == unit_val) ||
    279                (! json_is_string (unit_val)) ||
    280                (0 != strcmp (json_string_value (unit_val),
    281                              wgs->expected_unit)) )
    282           {
    283             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    284                         "Unexpected unit in wallet template\n");
    285             TALER_TESTING_interpreter_fail (wgs->is);
    286             return;
    287           }
    288           if ( (! json_is_boolean (allow_val)) ||
    289                (json_boolean_value (allow_val) !=
    290                 wgs->expected_unit_allow_fraction) )
    291           {
    292             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    293                         "Unexpected unit_allow_fraction in wallet template\n");
    294             TALER_TESTING_interpreter_fail (wgs->is);
    295             return;
    296           }
    297           if ( (! json_is_integer (prec_val)) ||
    298                ((uint32_t) json_integer_value (prec_val) !=
    299                 wgs->expected_unit_precision_level) )
    300           {
    301             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    302                         "Unexpected unit_precision_level in wallet template\n");
    303             TALER_TESTING_interpreter_fail (wgs->is);
    304             return;
    305           }
    306           found_unit = true;
    307         }
    308         if (product_id_matches (product,
    309                                 wgs->expected_product_id2))
    310           found_extra = true;
    311         if (product_id_matches (product,
    312                                 wgs->expected_product_id3))
    313           found_extra2 = true;
    314       }
    315       if (! found_unit || ! found_extra || ! found_extra2)
    316       {
    317         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    318                     "Expected product ids missing in wallet template\n");
    319         TALER_TESTING_interpreter_fail (wgs->is);
    320         return;
    321       }
    322     }
    323 
    324     if (NULL != wgs->expected_unit_name_short_i18n)
    325     {
    326       const json_t *units;
    327       bool found_unit_i18n = false;
    328 
    329       units = json_object_get (inventory_payload,
    330                                "units");
    331       if (! json_is_array (units))
    332       {
    333         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    334                     "Missing units in wallet template\n");
    335         TALER_TESTING_interpreter_fail (wgs->is);
    336         return;
    337       }
    338       for (size_t i = 0; i < json_array_size (units); i++)
    339       {
    340         const json_t *unit = json_array_get (units,
    341                                              i);
    342         const json_t *unit_id;
    343         const json_t *unit_i18n;
    344 
    345         unit_id = json_object_get (unit,
    346                                    "unit");
    347         if (! json_is_string (unit_id))
    348           continue;
    349         if (0 != strcmp (json_string_value (unit_id),
    350                          wgs->expected_unit))
    351           continue;
    352         unit_i18n = json_object_get (unit,
    353                                      "unit_name_short_i18n");
    354         if ( (NULL == unit_i18n) ||
    355              (! json_is_object (unit_i18n)) ||
    356              (1 != json_equal (unit_i18n,
    357                                wgs->expected_unit_name_short_i18n)) )
    358         {
    359           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    360                       "Unexpected unit_name_short_i18n in wallet template\n");
    361           TALER_TESTING_interpreter_fail (wgs->is);
    362           return;
    363         }
    364         found_unit_i18n = true;
    365         break;
    366       }
    367       if (! found_unit_i18n)
    368       {
    369         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    370                     "Expected unit entry missing in wallet template\n");
    371         TALER_TESTING_interpreter_fail (wgs->is);
    372         return;
    373       }
    374     }
    375 
    376     if ( (0 != wgs->expected_category_id1) ||
    377          (0 != wgs->expected_category_id2) )
    378     {
    379       const json_t *categories;
    380       bool found_cat1 = (0 == wgs->expected_category_id1);
    381       bool found_cat2 = (0 == wgs->expected_category_id2);
    382 
    383       categories = json_object_get (inventory_payload,
    384                                     "categories");
    385       if (! json_is_array (categories))
    386       {
    387         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    388                     "Missing categories in wallet template\n");
    389         TALER_TESTING_interpreter_fail (wgs->is);
    390         return;
    391       }
    392       for (size_t i = 0; i < json_array_size (categories); i++)
    393       {
    394         const json_t *category = json_array_get (categories,
    395                                                  i);
    396         const json_t *cid;
    397 
    398         cid = json_object_get (category,
    399                                "category_id");
    400         if (! json_is_integer (cid))
    401           continue;
    402         if ( (0 != wgs->expected_category_id1) &&
    403              ((uint64_t) json_integer_value (cid)
    404               == wgs->expected_category_id1) )
    405           found_cat1 = true;
    406         if ( (0 != wgs->expected_category_id2) &&
    407              ((uint64_t) json_integer_value (cid)
    408               == wgs->expected_category_id2) )
    409           found_cat2 = true;
    410       }
    411       if (! found_cat1 || ! found_cat2)
    412       {
    413         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    414                     "Expected category ids missing in wallet template\n");
    415         TALER_TESTING_interpreter_fail (wgs->is);
    416         return;
    417       }
    418     }
    419   }
    420 done:
    421   TALER_TESTING_interpreter_next (wgs->is);
    422 }
    423 
    424 
    425 /**
    426  * Run the "GET /templates/$ID" wallet CMD.
    427  *
    428  * @param cls closure.
    429  * @param cmd command being run now.
    430  * @param is interpreter state.
    431  */
    432 static void
    433 wallet_get_template_run (void *cls,
    434                          const struct TALER_TESTING_Command *cmd,
    435                          struct TALER_TESTING_Interpreter *is)
    436 {
    437   struct WalletGetTemplateState *wgs = cls;
    438 
    439   wgs->is = is;
    440   wgs->igh = TALER_MERCHANT_get_templates_create (
    441     TALER_TESTING_interpreter_get_context (is),
    442     wgs->merchant_url,
    443     wgs->template_id);
    444   {
    445     enum TALER_ErrorCode ec;
    446 
    447     ec = TALER_MERCHANT_get_templates_start (
    448       wgs->igh,
    449       &wallet_get_template_cb,
    450       wgs);
    451     GNUNET_assert (TALER_EC_NONE == ec);
    452   }
    453 }
    454 
    455 
    456 /**
    457  * Free the state of a "GET template" CMD, and possibly
    458  * cancel a pending operation thereof.
    459  *
    460  * @param cls closure.
    461  * @param cmd command being run.
    462  */
    463 static void
    464 wallet_get_template_cleanup (void *cls,
    465                              const struct TALER_TESTING_Command *cmd)
    466 {
    467   struct WalletGetTemplateState *wgs = cls;
    468 
    469   if (NULL != wgs->igh)
    470   {
    471     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    472                 "GET /templates/$ID operation did not complete\n");
    473     TALER_MERCHANT_get_templates_cancel (wgs->igh);
    474   }
    475   json_decref (wgs->expected_unit_name_short_i18n);
    476   GNUNET_free (wgs);
    477 }
    478 
    479 
    480 struct TALER_TESTING_Command
    481 TALER_TESTING_cmd_merchant_wallet_get_template (
    482   const char *label,
    483   const char *merchant_url,
    484   const char *template_id,
    485   size_t expected_products_len,
    486   const char *expected_product_id,
    487   const char *expected_unit,
    488   bool expected_unit_allow_fraction,
    489   uint32_t expected_unit_precision_level,
    490   json_t *expected_unit_name_short_i18n,
    491   const char *expected_product_id2,
    492   const char *expected_product_id3,
    493   uint64_t expected_category_id1,
    494   uint64_t expected_category_id2,
    495   const char *expected_exchange_url,
    496   const char *expected_wire_method,
    497   unsigned int http_status)
    498 {
    499   struct WalletGetTemplateState *wgs;
    500 
    501   wgs = GNUNET_new (struct WalletGetTemplateState);
    502   wgs->merchant_url = merchant_url;
    503   wgs->template_id = template_id;
    504   wgs->expected_products_len = expected_products_len;
    505   wgs->expected_product_id = expected_product_id;
    506   wgs->expected_unit = expected_unit;
    507   wgs->expected_unit_allow_fraction = expected_unit_allow_fraction;
    508   wgs->expected_unit_precision_level = expected_unit_precision_level;
    509   if (NULL != expected_unit_name_short_i18n)
    510     wgs->expected_unit_name_short_i18n =
    511       json_incref (expected_unit_name_short_i18n);
    512   wgs->expected_product_id2 = expected_product_id2;
    513   wgs->expected_product_id3 = expected_product_id3;
    514   wgs->expected_category_id1 = expected_category_id1;
    515   wgs->expected_category_id2 = expected_category_id2;
    516   wgs->expected_exchange_url = expected_exchange_url;
    517   wgs->expected_wire_method = expected_wire_method;
    518   wgs->http_status = http_status;
    519   {
    520     struct TALER_TESTING_Command cmd = {
    521       .cls = wgs,
    522       .label = label,
    523       .run = &wallet_get_template_run,
    524       .cleanup = &wallet_get_template_cleanup
    525     };
    526 
    527     return cmd;
    528   }
    529 }
    530 
    531 
    532 /* end of testing_api_cmd_wallet_get_template.c */