merchant

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

testing_api_cmd_get_orders.c (17336B)


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