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 (16043B)


      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->pos >= 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     if (hs->results_obtained != (uint64_t) hs->total)
    435     {
    436       /* bank returned fewer results than we expected */
    437       GNUNET_break (0);
    438       hs->failed = true;
    439       goto error;
    440     }
    441     TALER_TESTING_interpreter_next (is);
    442     return;
    443   case MHD_HTTP_NO_CONTENT:
    444     if (0 == hs->total)
    445     {
    446       /* not found is OK for empty history */
    447       TALER_TESTING_interpreter_next (is);
    448       return;
    449     }
    450     GNUNET_break (0);
    451     goto error;
    452   case MHD_HTTP_NOT_FOUND:
    453     if (0 == hs->total)
    454     {
    455       /* not found is OK for empty history */
    456       TALER_TESTING_interpreter_next (is);
    457       return;
    458     }
    459     GNUNET_break (0);
    460     goto error;
    461   default:
    462     hs->hh = NULL;
    463     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    464                 "Unwanted response code from /history/incoming: %u\n",
    465                 dhr->http_status);
    466     TALER_TESTING_interpreter_fail (is);
    467     return;
    468   }
    469 error:
    470   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    471               "Expected history of length %u, got %llu;"
    472               " HTTP status code: %u/%d, failed: %d\n",
    473               hs->total,
    474               (unsigned long long) hs->results_obtained,
    475               dhr->http_status,
    476               (int) dhr->ec,
    477               hs->failed ? 1 : 0);
    478   print_expected (hs->h,
    479                   hs->total,
    480                   UINT_MAX);
    481   TALER_TESTING_interpreter_fail (is);
    482 }
    483 
    484 
    485 /**
    486  * Run the command.
    487  *
    488  * @param cls closure.
    489  * @param cmd the command to execute.
    490  * @param is the interpreter state.
    491  */
    492 static void
    493 history_run (void *cls,
    494              const struct TALER_TESTING_Command *cmd,
    495              struct TALER_TESTING_Interpreter *is)
    496 {
    497   struct HistoryState *hs = cls;
    498   uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX;
    499   const uint64_t *row_ptr;
    500 
    501   (void) cmd;
    502   hs->is = is;
    503   /* Get row_id from trait. */
    504   if (NULL != hs->start_row_reference)
    505   {
    506     const struct TALER_TESTING_Command *history_cmd;
    507 
    508     history_cmd
    509       = TALER_TESTING_interpreter_lookup_command (is,
    510                                                   hs->start_row_reference);
    511 
    512     if (NULL == history_cmd)
    513       TALER_TESTING_FAIL (is);
    514     if (GNUNET_OK !=
    515         TALER_TESTING_get_trait_row (history_cmd,
    516                                      &row_ptr))
    517       TALER_TESTING_FAIL (is);
    518     else
    519       row_id = *row_ptr;
    520     TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
    521                      (unsigned long long) row_id);
    522   }
    523   hs->total = build_history (hs,
    524                              &hs->h);
    525   hs->hh = TALER_BANK_debit_history (
    526     TALER_TESTING_interpreter_get_context (is),
    527     &hs->auth,
    528     row_id,
    529     hs->num_results,
    530     GNUNET_TIME_UNIT_ZERO,
    531     &history_cb,
    532     hs);
    533   GNUNET_assert (NULL != hs->hh);
    534 }
    535 
    536 
    537 /**
    538  * Free the state from a "history" CMD, and possibly cancel
    539  * a pending operation thereof.
    540  *
    541  * @param cls closure.
    542  * @param cmd the command which is being cleaned up.
    543  */
    544 static void
    545 history_cleanup (void *cls,
    546                  const struct TALER_TESTING_Command *cmd)
    547 {
    548   struct HistoryState *hs = cls;
    549 
    550   (void) cmd;
    551   if (NULL != hs->hh)
    552   {
    553     TALER_TESTING_command_incomplete (hs->is,
    554                                       cmd->label);
    555     TALER_BANK_debit_history_cancel (hs->hh);
    556   }
    557   for (unsigned int off = 0; off<hs->total; off++)
    558   {
    559     GNUNET_free (hs->h[off].c_url);
    560   }
    561   GNUNET_free (hs->h);
    562   GNUNET_free (hs);
    563 }
    564 
    565 
    566 struct TALER_TESTING_Command
    567 TALER_TESTING_cmd_bank_debits (const char *label,
    568                                const struct TALER_BANK_AuthenticationData *auth,
    569                                const char *start_row_reference,
    570                                long long num_results)
    571 {
    572   struct HistoryState *hs;
    573 
    574   hs = GNUNET_new (struct HistoryState);
    575   hs->account_url = auth->wire_gateway_url;
    576   hs->start_row_reference = start_row_reference;
    577   hs->num_results = num_results;
    578   hs->auth = *auth;
    579 
    580   {
    581     struct TALER_TESTING_Command cmd = {
    582       .label = label,
    583       .cls = hs,
    584       .run = &history_run,
    585       .cleanup = &history_cleanup
    586     };
    587 
    588     return cmd;
    589   }
    590 }
    591 
    592 
    593 /* end of testing_api_cmd_bank_history_debit.c */