merchant

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

testing_api_cmd_get_orders.c (16820B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020-2023 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_get_orders.c
     21  * @brief command to test GET /orders
     22  * @author Jonathan Buchanan
     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 
     30 
     31 /**
     32  * State of a "GET orders" CMD.
     33  */
     34 struct GetOrdersState
     35 {
     36 
     37   /**
     38    * Handle for a "GET orders" request.
     39    */
     40   struct TALER_MERCHANT_OrdersGetHandle *ogh;
     41 
     42   /**
     43    * The interpreter state.
     44    */
     45   struct TALER_TESTING_Interpreter *is;
     46 
     47   /**
     48    * Base URL of the merchant serving the request.
     49    */
     50   const char *merchant_url;
     51 
     52   /**
     53    * Expected HTTP response code.
     54    */
     55   unsigned int http_status;
     56 
     57   /**
     58    * A NULL-terminated array of CMD labels that created orders.
     59    */
     60   const char **orders;
     61 
     62   /**
     63    * The length of @e orders.
     64    */
     65   unsigned int orders_length;
     66 
     67 };
     68 
     69 
     70 /**
     71  * Callback for a GET /orders operation.
     72  *
     73  * @param cls closure for this function
     74  * @param ogr response
     75  */
     76 static void
     77 get_orders_cb (void *cls,
     78                const struct TALER_MERCHANT_OrdersGetResponse *ogr)
     79 {
     80   struct GetOrdersState *gos = cls;
     81 
     82   gos->ogh = NULL;
     83   if (gos->http_status != ogr->hr.http_status)
     84   {
     85     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     86                 "Unexpected response code %u (%d) to command %s\n",
     87                 ogr->hr.http_status,
     88                 (int) ogr->hr.ec,
     89                 TALER_TESTING_interpreter_get_current_label (gos->is));
     90     TALER_TESTING_interpreter_fail (gos->is);
     91     return;
     92   }
     93   switch (ogr->hr.http_status)
     94   {
     95   case MHD_HTTP_OK:
     96     if (ogr->details.ok.orders_length != gos->orders_length)
     97     {
     98       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     99                   "Number of orders found does not match\n");
    100       TALER_TESTING_interpreter_fail (gos->is);
    101       return;
    102     }
    103     for (unsigned int i = 0; i < ogr->details.ok.orders_length; ++i)
    104     {
    105       const struct TALER_MERCHANT_OrderEntry *order =
    106         &ogr->details.ok.orders[i];
    107       const struct TALER_TESTING_Command *order_cmd;
    108 
    109       order_cmd = TALER_TESTING_interpreter_lookup_command (
    110         gos->is,
    111         gos->orders[i]);
    112 
    113       {
    114         const char *order_id;
    115 
    116         if (GNUNET_OK !=
    117             TALER_TESTING_get_trait_order_id (order_cmd,
    118                                               &order_id))
    119         {
    120           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    121                       "Could not fetch order id\n");
    122           TALER_TESTING_interpreter_fail (gos->is);
    123           return;
    124         }
    125         if (0 != strcmp (order->order_id,
    126                          order_id))
    127         {
    128           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    129                       "Order id does not match\n");
    130           TALER_TESTING_interpreter_fail (gos->is);
    131           return;
    132         }
    133       }
    134       {
    135         const json_t *contract_terms;
    136         struct TALER_Amount amount;
    137         const char *summary;
    138         struct GNUNET_JSON_Specification spec[] = {
    139           GNUNET_JSON_spec_string ("summary",
    140                                    &summary),
    141           TALER_JSON_spec_amount_any ("amount",
    142                                       &amount),
    143           GNUNET_JSON_spec_end ()
    144         };
    145 
    146         if (GNUNET_OK !=
    147             TALER_TESTING_get_trait_contract_terms (order_cmd,
    148                                                     &contract_terms))
    149         {
    150           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    151                       "Could not fetch order contract terms\n");
    152           TALER_TESTING_interpreter_fail (gos->is);
    153           return;
    154         }
    155         if (GNUNET_OK !=
    156             GNUNET_JSON_parse (contract_terms,
    157                                spec,
    158                                NULL, NULL))
    159         {
    160           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    161                       "Could not parse order contract terms\n");
    162           TALER_TESTING_interpreter_fail (gos->is);
    163           return;
    164         }
    165         if ((0 != strcmp (summary,
    166                           order->summary)) ||
    167             (GNUNET_OK != TALER_amount_cmp_currency (&amount,
    168                                                      &order->amount)) ||
    169             (0 != TALER_amount_cmp (&amount,
    170                                     &order->amount)))
    171         {
    172           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    173                       "Order summary and/or amount does not match\n");
    174           TALER_TESTING_interpreter_fail (gos->is);
    175           return;
    176         }
    177       }
    178     }
    179     break;
    180   case MHD_HTTP_ACCEPTED:
    181     /* FIXME: do more checks here (new KYC logic!) */
    182     break;
    183   default:
    184     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    185                 "Unhandled HTTP status.\n");
    186   }
    187   TALER_TESTING_interpreter_next (gos->is);
    188 }
    189 
    190 
    191 /**
    192  * Run the "GET /orders" CMD.
    193  *
    194  * @param cls closure.
    195  * @param cmd command being run now.
    196  * @param is interpreter state.
    197  */
    198 static void
    199 get_orders_run (void *cls,
    200                 const struct TALER_TESTING_Command *cmd,
    201                 struct TALER_TESTING_Interpreter *is)
    202 {
    203   struct GetOrdersState *gos = cls;
    204 
    205   gos->is = is;
    206   gos->ogh = TALER_MERCHANT_orders_get (TALER_TESTING_interpreter_get_context (
    207                                           is),
    208                                         gos->merchant_url,
    209                                         &get_orders_cb,
    210                                         gos);
    211   GNUNET_assert (NULL != gos->ogh);
    212 }
    213 
    214 
    215 /**
    216  * Free the state of a "GET orders" CMD, and possibly
    217  * cancel a pending operation thereof.
    218  *
    219  * @param cls closure.
    220  * @param cmd command being run.
    221  */
    222 static void
    223 get_orders_cleanup (void *cls,
    224                     const struct TALER_TESTING_Command *cmd)
    225 {
    226   struct GetOrdersState *gos = cls;
    227 
    228   if (NULL != gos->ogh)
    229   {
    230     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    231                 "GET /orders operation did not complete\n");
    232     TALER_MERCHANT_orders_get_cancel (gos->ogh);
    233   }
    234   GNUNET_array_grow (gos->orders,
    235                      gos->orders_length,
    236                      0);
    237   GNUNET_free (gos);
    238 }
    239 
    240 
    241 struct TALER_TESTING_Command
    242 TALER_TESTING_cmd_merchant_get_orders (const char *label,
    243                                        const char *merchant_url,
    244                                        unsigned int http_status,
    245                                        ...)
    246 {
    247   struct GetOrdersState *gos;
    248 
    249   gos = GNUNET_new (struct GetOrdersState);
    250   gos->merchant_url = merchant_url;
    251   gos->http_status = http_status;
    252   {
    253     const char *clabel;
    254     va_list ap;
    255 
    256     va_start (ap, http_status);
    257     while (NULL != (clabel = va_arg (ap, const char *)))
    258     {
    259       GNUNET_array_append (gos->orders,
    260                            gos->orders_length,
    261                            clabel);
    262     }
    263     va_end (ap);
    264   }
    265   {
    266     struct TALER_TESTING_Command cmd = {
    267       .cls = gos,
    268       .label = label,
    269       .run = &get_orders_run,
    270       .cleanup = &get_orders_cleanup
    271     };
    272 
    273     return cmd;
    274   }
    275 }
    276 
    277 
    278 struct MerchantPollOrdersConcludeState
    279 {
    280   /**
    281    * The interpreter state.
    282    */
    283   struct TALER_TESTING_Interpreter *is;
    284 
    285   /**
    286    * Reference to a command that can provide a poll orders start command.
    287    */
    288   const char *start_reference;
    289 
    290   /**
    291    * Task to wait for the deadline.
    292    */
    293   struct GNUNET_SCHEDULER_Task *task;
    294 
    295   /**
    296    * Expected HTTP response status code.
    297    */
    298   unsigned int expected_http_status;
    299 };
    300 
    301 
    302 struct MerchantPollOrdersStartState
    303 {
    304   /**
    305    * The merchant base URL.
    306    */
    307   const char *merchant_url;
    308 
    309   /**
    310    * The handle to the current GET /private/orders request.
    311    */
    312   struct TALER_MERCHANT_OrdersGetHandle *ogh;
    313 
    314   /**
    315    * The interpreter state.
    316    */
    317   struct TALER_TESTING_Interpreter *is;
    318 
    319   /**
    320    * How long to wait for server to return a response.
    321    */
    322   struct GNUNET_TIME_Relative timeout;
    323 
    324   /**
    325    * Conclude state waiting for completion (if any).
    326    */
    327   struct MerchantPollOrdersConcludeState *cs;
    328 
    329   /**
    330    * The HTTP status code returned by the backend.
    331    */
    332   unsigned int http_status;
    333 
    334   /**
    335    * When the request should be completed by.
    336    */
    337   struct GNUNET_TIME_Absolute deadline;
    338 };
    339 
    340 
    341 /**
    342  * Task called when either the timeout for the get orders
    343  * command expired or we got a response.  Checks if the
    344  * result is what we expected.
    345  *
    346  * @param cls a `struct MerchantPollOrdersConcludeState`
    347  */
    348 static void
    349 conclude_task (void *cls)
    350 {
    351   struct MerchantPollOrdersConcludeState *poc = cls;
    352   const struct TALER_TESTING_Command *poll_cmd;
    353   struct MerchantPollOrdersStartState *pos;
    354   struct GNUNET_TIME_Absolute now;
    355 
    356   poc->task = NULL;
    357   poll_cmd =
    358     TALER_TESTING_interpreter_lookup_command (poc->is,
    359                                               poc->start_reference);
    360   if (NULL == poll_cmd)
    361     TALER_TESTING_FAIL (poc->is);
    362   pos = poll_cmd->cls;
    363   if (NULL != pos->ogh)
    364   {
    365     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    366                 "Expected poll GET /private/orders to have completed, but it did not!\n");
    367     TALER_TESTING_FAIL (poc->is);
    368   }
    369   if (pos->http_status != poc->expected_http_status)
    370   {
    371     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    372                 "Expected HTTP status %u, got %u\n",
    373                 poc->expected_http_status,
    374                 pos->http_status);
    375     TALER_TESTING_FAIL (poc->is);
    376   }
    377   now = GNUNET_TIME_absolute_get ();
    378   if (GNUNET_TIME_absolute_cmp (GNUNET_TIME_absolute_add (
    379                                   pos->deadline,
    380                                   GNUNET_TIME_UNIT_SECONDS),
    381                                 <,
    382                                 now))
    383   {
    384     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    385                 "Expected answer to be delayed until %llu, but got response at %llu\n",
    386                 (unsigned long long) pos->deadline.abs_value_us,
    387                 (unsigned long long) now.abs_value_us);
    388     TALER_TESTING_FAIL (poc->is);
    389   }
    390   TALER_TESTING_interpreter_next (poc->is);
    391 }
    392 
    393 
    394 /**
    395  * Callback to process a GET /orders request
    396  *
    397  * @param cls closure
    398  * @param ogr response details
    399  */
    400 static void
    401 merchant_poll_orders_cb (
    402   void *cls,
    403   const struct TALER_MERCHANT_OrdersGetResponse *ogr)
    404 {
    405   struct MerchantPollOrdersStartState *pos = cls;
    406 
    407   pos->ogh = NULL;
    408   if (MHD_HTTP_OK != ogr->hr.http_status)
    409   {
    410     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    411                 "Unexpected response code %u (%d) to command %s\n",
    412                 ogr->hr.http_status,
    413                 (int) ogr->hr.ec,
    414                 TALER_TESTING_interpreter_get_current_label (pos->is));
    415     TALER_TESTING_interpreter_fail (pos->is);
    416     return;
    417   }
    418   switch (ogr->hr.http_status)
    419   {
    420   case MHD_HTTP_OK:
    421     // FIXME: use order references to check if the data returned matches that from the POST / PATCH
    422     break;
    423   default:
    424     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    425                 "Unhandled HTTP status.\n");
    426   }
    427   pos->http_status = ogr->hr.http_status;
    428   if (NULL != pos->cs)
    429   {
    430     GNUNET_SCHEDULER_cancel (pos->cs->task);
    431     pos->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
    432                                               pos->cs);
    433   }
    434 }
    435 
    436 
    437 /**
    438  * Run the "GET orders" CMD.
    439  *
    440  * @param cls closure.
    441  * @param cmd command being run now.
    442  * @param is interpreter state.
    443  */
    444 static void
    445 merchant_poll_orders_start_run (void *cls,
    446                                 const struct TALER_TESTING_Command *cmd,
    447                                 struct TALER_TESTING_Interpreter *is)
    448 {
    449   struct MerchantPollOrdersStartState *pos = cls;
    450 
    451   /* add 1s grace time to timeout */
    452   pos->deadline
    453     = GNUNET_TIME_relative_to_absolute (
    454         GNUNET_TIME_relative_add (pos->timeout,
    455                                   GNUNET_TIME_UNIT_SECONDS));
    456   pos->is = is;
    457   pos->ogh = TALER_MERCHANT_orders_get2 (TALER_TESTING_interpreter_get_context (
    458                                            is),
    459                                          pos->merchant_url,
    460                                          TALER_EXCHANGE_YNA_ALL,
    461                                          TALER_EXCHANGE_YNA_ALL,
    462                                          TALER_EXCHANGE_YNA_ALL,
    463                                          GNUNET_TIME_UNIT_ZERO_TS,
    464                                          1,
    465                                          2,
    466                                          pos->timeout,
    467                                          &merchant_poll_orders_cb,
    468                                          pos);
    469   GNUNET_assert (NULL != pos->ogh);
    470   /* We CONTINUE to run the interpreter while the long-polled command
    471      completes asynchronously! */
    472   TALER_TESTING_interpreter_next (pos->is);
    473 }
    474 
    475 
    476 /**
    477  * Free the state of a "GET orders" CMD, and possibly
    478  * cancel a pending operation thereof.
    479  *
    480  * @param cls closure.
    481  * @param cmd command being run.
    482  */
    483 static void
    484 merchant_poll_orders_start_cleanup (void *cls,
    485                                     const struct TALER_TESTING_Command *cmd)
    486 {
    487   struct MerchantPollOrdersStartState *pos = cls;
    488 
    489   if (NULL != pos->ogh)
    490   {
    491     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    492                 "Command `%s' was not terminated\n",
    493                 TALER_TESTING_interpreter_get_current_label (
    494                   pos->is));
    495     TALER_MERCHANT_orders_get_cancel (pos->ogh);
    496   }
    497   GNUNET_free (pos);
    498 }
    499 
    500 
    501 struct TALER_TESTING_Command
    502 TALER_TESTING_cmd_poll_orders_start (const char *label,
    503                                      const char *merchant_url,
    504                                      struct GNUNET_TIME_Relative timeout)
    505 {
    506   struct MerchantPollOrdersStartState *pos;
    507 
    508   pos = GNUNET_new (struct MerchantPollOrdersStartState);
    509   pos->merchant_url = merchant_url;
    510   pos->timeout = timeout;
    511   {
    512     struct TALER_TESTING_Command cmd = {
    513       .cls = pos,
    514       .label = label,
    515       .run = &merchant_poll_orders_start_run,
    516       .cleanup = &merchant_poll_orders_start_cleanup
    517     };
    518 
    519     return cmd;
    520   }
    521 }
    522 
    523 
    524 /**
    525  * Wait for the "GET orders" CMD to complete.
    526  *
    527  * @param cls closure.
    528  * @param cmd command being run now.
    529  * @param is interpreter state.
    530  */
    531 static void
    532 merchant_poll_orders_conclude_run (void *cls,
    533                                    const struct TALER_TESTING_Command *cmd,
    534                                    struct TALER_TESTING_Interpreter *is)
    535 {
    536   struct MerchantPollOrdersConcludeState *poc = cls;
    537   const struct TALER_TESTING_Command *poll_cmd;
    538   struct MerchantPollOrdersStartState *pos;
    539 
    540   poc->is = is;
    541   poll_cmd =
    542     TALER_TESTING_interpreter_lookup_command (is,
    543                                               poc->start_reference);
    544   if (NULL == poll_cmd)
    545     TALER_TESTING_FAIL (poc->is);
    546   GNUNET_assert (poll_cmd->run == &merchant_poll_orders_start_run);
    547   pos = poll_cmd->cls;
    548   pos->cs = poc;
    549   if (NULL == pos->ogh)
    550     poc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
    551                                           poc);
    552   else
    553     poc->task = GNUNET_SCHEDULER_add_at (pos->deadline,
    554                                          &conclude_task,
    555                                          poc);
    556 }
    557 
    558 
    559 /**
    560  * Free the state of a "GET orders" CMD, and possibly
    561  * cancel a pending operation thereof.
    562  *
    563  * @param cls closure.
    564  * @param cmd command being run.
    565  */
    566 static void
    567 merchant_poll_orders_conclude_cleanup (void *cls,
    568                                        const struct TALER_TESTING_Command *cmd)
    569 {
    570   struct MerchantPollOrdersConcludeState *poc = cls;
    571 
    572   if (NULL != poc->task)
    573   {
    574     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    575                 "Command `%s' was not terminated\n",
    576                 TALER_TESTING_interpreter_get_current_label (
    577                   poc->is));
    578     GNUNET_SCHEDULER_cancel (poc->task);
    579     poc->task = NULL;
    580   }
    581   GNUNET_free (poc);
    582 }
    583 
    584 
    585 struct TALER_TESTING_Command
    586 TALER_TESTING_cmd_poll_orders_conclude (const char *label,
    587                                         unsigned int http_status,
    588                                         const char *poll_start_reference)
    589 {
    590   struct MerchantPollOrdersConcludeState *poc;
    591 
    592   poc = GNUNET_new (struct MerchantPollOrdersConcludeState);
    593   poc->start_reference = poll_start_reference;
    594   poc->expected_http_status = http_status;
    595   {
    596     struct TALER_TESTING_Command cmd = {
    597       .cls = poc,
    598       .label = label,
    599       .run = &merchant_poll_orders_conclude_run,
    600       .cleanup = &merchant_poll_orders_conclude_cleanup
    601     };
    602 
    603     return cmd;
    604   }
    605 }
    606 
    607 
    608 /* end of testing_api_cmd_get_orders.c */