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


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