merchant

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

testing_api_cmd_get_orders.c (18082B)


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