exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

testing_api_cmd_bank_history_debit.c (15857B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2018-2021 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/testing_api_cmd_bank_history_debit.c
     21  * @brief command to check the /history/outgoing API from the bank.
     22  * @author Marcello Stanisci
     23  */
     24 #include "taler/taler_json_lib.h"
     25 #include <gnunet/gnunet_curl_lib.h>
     26 #include "taler/taler_testing_lib.h"
     27 #include "taler/taler_fakebank_lib.h"
     28 #include "taler/taler_bank_service.h"
     29 #include "taler/taler_fakebank_lib.h"
     30 
     31 /**
     32  * Item in the transaction history, as reconstructed from the
     33  * command history.
     34  */
     35 struct History
     36 {
     37 
     38   /**
     39    * Wire details.
     40    */
     41   struct TALER_BANK_DebitDetails details;
     42 
     43   /**
     44    * Serial ID of the wire transfer.
     45    */
     46   uint64_t row_id;
     47 
     48   /**
     49    * URL to free.
     50    */
     51   char *c_url;
     52 
     53 };
     54 
     55 
     56 /**
     57  * State for a "history" CMD.
     58  */
     59 struct HistoryState
     60 {
     61   /**
     62    * Base URL of the account offering the "history" operation.
     63    */
     64   const char *account_url;
     65 
     66   /**
     67    * Reference to command defining the
     68    * first row number we want in the result.
     69    */
     70   const char *start_row_reference;
     71 
     72   /**
     73    * How many rows we want in the result, _at most_,
     74    * and ascending/descending.
     75    */
     76   long long num_results;
     77 
     78   /**
     79    * Login data to use to authenticate.
     80    */
     81   struct TALER_BANK_AuthenticationData auth;
     82 
     83   /**
     84    * Handle to a pending "history" operation.
     85    */
     86   struct TALER_BANK_DebitHistoryHandle *hh;
     87 
     88   /**
     89    * Our interpreter.
     90    */
     91   struct TALER_TESTING_Interpreter *is;
     92 
     93   /**
     94    * Expected number of results (= rows).
     95    */
     96   uint64_t results_obtained;
     97 
     98   /**
     99    * Set to #GNUNET_YES if the callback detects something
    100    * unexpected.
    101    */
    102   int failed;
    103 
    104   /**
    105    * Expected history.
    106    */
    107   struct History *h;
    108 
    109   /**
    110    * Length of @e h
    111    */
    112   unsigned int total;
    113 
    114 };
    115 
    116 
    117 /**
    118  * Log which history we expected.  Called when an error occurs.
    119  *
    120  * @param h what we expected.
    121  * @param h_len number of entries in @a h.
    122  * @param off position of the mismatch.
    123  */
    124 static void
    125 print_expected (struct History *h,
    126                 unsigned int h_len,
    127                 unsigned int off)
    128 {
    129   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    130               "Transaction history (debit) mismatch at position %u/%u\n",
    131               off,
    132               h_len);
    133   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    134               "Expected history:\n");
    135   for (unsigned int i = 0; i<h_len; i++)
    136   {
    137     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    138                 "H(%u): %s (serial: %llu, subject: %s, counterpart: %s)\n",
    139                 i,
    140                 TALER_amount2s (&h[i].details.amount),
    141                 (unsigned long long) h[i].row_id,
    142                 TALER_B2S (&h[i].details.wtid),
    143                 h[i].details.credit_account_uri.full_payto);
    144   }
    145 }
    146 
    147 
    148 /**
    149  * Closure for command_cb().
    150  */
    151 struct IteratorContext
    152 {
    153   /**
    154    * Array of history items to return.
    155    */
    156   struct History *h;
    157 
    158   /**
    159    * Set to the row ID from where on we should actually process history items,
    160    * or NULL if we should process all of them.
    161    */
    162   const uint64_t *row_id_start;
    163 
    164   /**
    165    * History state we are working on.
    166    */
    167   struct HistoryState *hs;
    168 
    169   /**
    170    * Current length of the @e h array.
    171    */
    172   unsigned int total;
    173 
    174   /**
    175    * Current write position in @e h array.
    176    */
    177   unsigned int pos;
    178 
    179   /**
    180    * Ok equals True whenever a starting row_id was provided AND was found
    181    * among the CMDs, OR no starting row was given in the first place.
    182    */
    183   bool ok;
    184 
    185 };
    186 
    187 
    188 /**
    189  * Helper function of build_history() that expands
    190  * the history for each relevant command encountered.
    191  *
    192  * @param[in,out] cls our `struct IteratorContext`
    193  * @param cmd a command to process
    194  */
    195 static void
    196 command_cb (void *cls,
    197             const struct TALER_TESTING_Command *cmd)
    198 {
    199   struct IteratorContext *ic = cls;
    200   struct HistoryState *hs = ic->hs;
    201   const uint64_t *row_id;
    202   const struct TALER_FullPayto *debit_account;
    203   const struct TALER_FullPayto *credit_account;
    204   const struct TALER_Amount *amount;
    205   const struct TALER_WireTransferIdentifierRawP *wtid;
    206   const char *exchange_base_url;
    207 
    208   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    209               "Checking if command %s is relevant for debit history\n",
    210               cmd->label);
    211   if ( (GNUNET_OK !=
    212         TALER_TESTING_get_trait_bank_row (cmd,
    213                                           &row_id)) ||
    214        (GNUNET_OK !=
    215         TALER_TESTING_get_trait_debit_payto_uri (cmd,
    216                                                  &debit_account)) ||
    217        (GNUNET_OK !=
    218         TALER_TESTING_get_trait_credit_payto_uri (cmd,
    219                                                   &credit_account)) ||
    220        (GNUNET_OK !=
    221         TALER_TESTING_get_trait_amount (cmd,
    222                                         &amount)) ||
    223        (GNUNET_OK !=
    224         TALER_TESTING_get_trait_wtid (cmd,
    225                                       &wtid)) ||
    226        (GNUNET_OK !=
    227         TALER_TESTING_get_trait_exchange_url (cmd,
    228                                               &exchange_base_url)) )
    229     return;   /* not an event we care about */
    230   /* Seek "/history/outgoing" starting row.  */
    231   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    232               "Command %s is relevant for debit history!\n",
    233               cmd->label);
    234   if ( (NULL != ic->row_id_start) &&
    235        (*(ic->row_id_start) == *row_id) &&
    236        (! ic->ok) )
    237   {
    238     /* Until here, nothing counted. */
    239     ic->ok = true;
    240     return;
    241   }
    242   /* when 'start' was _not_ given, then ok == GNUNET_YES */
    243   if (! ic->ok)
    244     return;   /* skip until we find the marker */
    245   if (ic->total >= GNUNET_MAX (hs->num_results,
    246                                -hs->num_results) )
    247   {
    248     TALER_LOG_DEBUG ("Hit history limit\n");
    249     return;
    250   }
    251   TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
    252                   debit_account->full_payto,
    253                   credit_account->full_payto,
    254                   hs->account_url);
    255   /* found matching record, make sure we have room */
    256   if (ic->pos == ic->total)
    257     GNUNET_array_grow (ic->h,
    258                        ic->total,
    259                        ic->pos * 2);
    260   ic->h[ic->pos].c_url = GNUNET_strdup (credit_account->full_payto);
    261   ic->h[ic->pos].details.credit_account_uri.full_payto
    262     = ic->h[ic->pos].c_url;
    263   ic->h[ic->pos].details.amount = *amount;
    264   ic->h[ic->pos].row_id = *row_id;
    265   ic->h[ic->pos].details.wtid = *wtid;
    266   ic->h[ic->pos].details.exchange_base_url = exchange_base_url;
    267   ic->pos++;
    268 }
    269 
    270 
    271 /**
    272  * This function constructs the list of history elements that
    273  * interest the account number of the caller.  It has two main
    274  * loops: the first to figure out how many history elements have
    275  * to be allocated, and the second to actually populate every
    276  * element.
    277  *
    278  * @param hs history state command context
    279  * @param[out] rh history array to initialize.
    280  * @return number of entries in @a rh.
    281  */
    282 static unsigned int
    283 build_history (struct HistoryState *hs,
    284                struct History **rh)
    285 {
    286   struct TALER_TESTING_Interpreter *is = hs->is;
    287   struct IteratorContext ic = {
    288     .hs = hs
    289   };
    290 
    291   if (NULL != hs->start_row_reference)
    292   {
    293     const struct TALER_TESTING_Command *add_incoming_cmd;
    294 
    295     TALER_LOG_INFO (
    296       "`%s': start row given via reference `%s'\n",
    297       TALER_TESTING_interpreter_get_current_label  (is),
    298       hs->start_row_reference);
    299     add_incoming_cmd = TALER_TESTING_interpreter_lookup_command (
    300       is,
    301       hs->start_row_reference);
    302     GNUNET_assert (NULL != add_incoming_cmd);
    303     GNUNET_assert (GNUNET_OK ==
    304                    TALER_TESTING_get_trait_row (add_incoming_cmd,
    305                                                 &ic.row_id_start));
    306   }
    307 
    308   ic.ok = false;
    309   if (NULL == ic.row_id_start)
    310     ic.ok = true;
    311   GNUNET_array_grow (ic.h,
    312                      ic.total,
    313                      4);
    314   GNUNET_assert (0 != hs->num_results);
    315   TALER_TESTING_iterate (is,
    316                          hs->num_results > 0,
    317                          &command_cb,
    318                          &ic);
    319   GNUNET_assert (ic.ok);
    320   GNUNET_array_grow (ic.h,
    321                      ic.total,
    322                      ic.pos);
    323   if (0 == ic.pos)
    324     TALER_LOG_DEBUG ("Empty credit history computed\n");
    325   *rh = ic.h;
    326   return ic.pos;
    327 }
    328 
    329 
    330 /**
    331  * Check that the "/history/outgoing" response matches the
    332  * CMD whose offset in the list of CMDs is @a off.
    333  *
    334  * @param h expected history
    335  * @param total number of entries in @a h
    336  * @param off the offset (of the CMD list) where the command
    337  *        to check is.
    338  * @param details the expected transaction details.
    339  * @return #GNUNET_OK if the transaction is what we expect.
    340  */
    341 static enum GNUNET_GenericReturnValue
    342 check_result (struct History *h,
    343               uint64_t total,
    344               unsigned int off,
    345               const struct TALER_BANK_DebitDetails *details)
    346 {
    347   if (off >= total)
    348   {
    349     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    350                 "Test says history has at most %u"
    351                 " results, but got result #%u to check\n",
    352                 (unsigned int) total,
    353                 off);
    354     print_expected (h,
    355                     total,
    356                     off);
    357     return GNUNET_SYSERR;
    358   }
    359   if ( (0 != GNUNET_memcmp (&h[off].details.wtid,
    360                             &details->wtid)) ||
    361        (0 != TALER_amount_cmp (&h[off].details.amount,
    362                                &details->amount)) ||
    363        (0 != TALER_full_payto_normalize_and_cmp (
    364           h[off].details.credit_account_uri,
    365           details->credit_account_uri)) )
    366   {
    367     GNUNET_break (0);
    368     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    369                 "expected debit_account_uri: %s with %s for %s\n",
    370                 h[off].details.credit_account_uri.full_payto,
    371                 TALER_amount2s (&h[off].details.amount),
    372                 TALER_B2S (&h[off].details.wtid));
    373     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    374                 "actual debit_account_uri: %s with %s for %s\n",
    375                 details->credit_account_uri.full_payto,
    376                 TALER_amount2s (&details->amount),
    377                 TALER_B2S (&details->wtid));
    378     print_expected (h,
    379                     total,
    380                     off);
    381     return GNUNET_SYSERR;
    382   }
    383   return GNUNET_OK;
    384 }
    385 
    386 
    387 /**
    388  * This callback will (1) check that the HTTP response code
    389  * is acceptable and (2) that the history is consistent.  The
    390  * consistency is checked by going through all the past CMDs,
    391  * reconstructing then the expected history as of those, and
    392  * finally check it against what the bank returned.
    393  *
    394  * @param cls closure.
    395  * @param dhr http response details
    396  */
    397 static void
    398 history_cb (void *cls,
    399             const struct TALER_BANK_DebitHistoryResponse *dhr)
    400 {
    401   struct HistoryState *hs = cls;
    402   struct TALER_TESTING_Interpreter *is = hs->is;
    403 
    404   hs->hh = NULL;
    405   switch (dhr->http_status)
    406   {
    407   case 0:
    408     GNUNET_break (0);
    409     goto error;
    410   case MHD_HTTP_OK:
    411     for (unsigned int i = 0; i<dhr->details.ok.details_length; i++)
    412     {
    413       const struct TALER_BANK_DebitDetails *dd =
    414         &dhr->details.ok.details[i];
    415 
    416       /* check current element */
    417       if (GNUNET_OK !=
    418           check_result (hs->h,
    419                         hs->total,
    420                         hs->results_obtained,
    421                         dd))
    422       {
    423         GNUNET_break (0);
    424         json_dumpf (dhr->response,
    425                     stderr,
    426                     JSON_COMPACT);
    427         hs->failed = true;
    428         hs->hh = NULL;
    429         TALER_TESTING_interpreter_fail (is);
    430         return;
    431       }
    432       hs->results_obtained++;
    433     }
    434     TALER_TESTING_interpreter_next (is);
    435     return;
    436   case MHD_HTTP_NO_CONTENT:
    437     if (0 == hs->total)
    438     {
    439       /* not found is OK for empty history */
    440       TALER_TESTING_interpreter_next (is);
    441       return;
    442     }
    443     GNUNET_break (0);
    444     goto error;
    445   case MHD_HTTP_NOT_FOUND:
    446     if (0 == hs->total)
    447     {
    448       /* not found is OK for empty history */
    449       TALER_TESTING_interpreter_next (is);
    450       return;
    451     }
    452     GNUNET_break (0);
    453     goto error;
    454   default:
    455     hs->hh = NULL;
    456     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    457                 "Unwanted response code from /history/incoming: %u\n",
    458                 dhr->http_status);
    459     TALER_TESTING_interpreter_fail (is);
    460     return;
    461   }
    462 error:
    463   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    464               "Expected history of length %u, got %llu;"
    465               " HTTP status code: %u/%d, failed: %d\n",
    466               hs->total,
    467               (unsigned long long) hs->results_obtained,
    468               dhr->http_status,
    469               (int) dhr->ec,
    470               hs->failed ? 1 : 0);
    471   print_expected (hs->h,
    472                   hs->total,
    473                   UINT_MAX);
    474   TALER_TESTING_interpreter_fail (is);
    475 }
    476 
    477 
    478 /**
    479  * Run the command.
    480  *
    481  * @param cls closure.
    482  * @param cmd the command to execute.
    483  * @param is the interpreter state.
    484  */
    485 static void
    486 history_run (void *cls,
    487              const struct TALER_TESTING_Command *cmd,
    488              struct TALER_TESTING_Interpreter *is)
    489 {
    490   struct HistoryState *hs = cls;
    491   uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX;
    492   const uint64_t *row_ptr;
    493 
    494   (void) cmd;
    495   hs->is = is;
    496   /* Get row_id from trait. */
    497   if (NULL != hs->start_row_reference)
    498   {
    499     const struct TALER_TESTING_Command *history_cmd;
    500 
    501     history_cmd
    502       = TALER_TESTING_interpreter_lookup_command (is,
    503                                                   hs->start_row_reference);
    504 
    505     if (NULL == history_cmd)
    506       TALER_TESTING_FAIL (is);
    507     if (GNUNET_OK !=
    508         TALER_TESTING_get_trait_row (history_cmd,
    509                                      &row_ptr))
    510       TALER_TESTING_FAIL (is);
    511     else
    512       row_id = *row_ptr;
    513     TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
    514                      (unsigned long long) row_id);
    515   }
    516   hs->total = build_history (hs,
    517                              &hs->h);
    518   hs->hh = TALER_BANK_debit_history (
    519     TALER_TESTING_interpreter_get_context (is),
    520     &hs->auth,
    521     row_id,
    522     hs->num_results,
    523     GNUNET_TIME_UNIT_ZERO,
    524     &history_cb,
    525     hs);
    526   GNUNET_assert (NULL != hs->hh);
    527 }
    528 
    529 
    530 /**
    531  * Free the state from a "history" CMD, and possibly cancel
    532  * a pending operation thereof.
    533  *
    534  * @param cls closure.
    535  * @param cmd the command which is being cleaned up.
    536  */
    537 static void
    538 history_cleanup (void *cls,
    539                  const struct TALER_TESTING_Command *cmd)
    540 {
    541   struct HistoryState *hs = cls;
    542 
    543   (void) cmd;
    544   if (NULL != hs->hh)
    545   {
    546     TALER_TESTING_command_incomplete (hs->is,
    547                                       cmd->label);
    548     TALER_BANK_debit_history_cancel (hs->hh);
    549   }
    550   for (unsigned int off = 0; off<hs->total; off++)
    551   {
    552     GNUNET_free (hs->h[off].c_url);
    553   }
    554   GNUNET_free (hs->h);
    555   GNUNET_free (hs);
    556 }
    557 
    558 
    559 struct TALER_TESTING_Command
    560 TALER_TESTING_cmd_bank_debits (const char *label,
    561                                const struct TALER_BANK_AuthenticationData *auth,
    562                                const char *start_row_reference,
    563                                long long num_results)
    564 {
    565   struct HistoryState *hs;
    566 
    567   hs = GNUNET_new (struct HistoryState);
    568   hs->account_url = auth->wire_gateway_url;
    569   hs->start_row_reference = start_row_reference;
    570   hs->num_results = num_results;
    571   hs->auth = *auth;
    572 
    573   {
    574     struct TALER_TESTING_Command cmd = {
    575       .label = label,
    576       .cls = hs,
    577       .run = &history_run,
    578       .cleanup = &history_cleanup
    579     };
    580 
    581     return cmd;
    582   }
    583 }
    584 
    585 
    586 /* end of testing_api_cmd_bank_history_debit.c */