LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_bank_history_debit.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 61.8 % 170 105
Test Date: 2026-10-17 15:46:49 Functions: 87.5 % 8 7

            Line data    Source code
       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            0 : print_expected (struct History *h,
     126              :                 unsigned int h_len,
     127              :                 unsigned int off)
     128              : {
     129            0 :   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     130              :               "Transaction history (debit) mismatch at position %u/%u\n",
     131              :               off,
     132              :               h_len);
     133            0 :   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     134              :               "Expected history:\n");
     135            0 :   for (unsigned int i = 0; i<h_len; i++)
     136              :   {
     137            0 :     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            0 : }
     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           30 : command_cb (void *cls,
     197              :             const struct TALER_TESTING_Command *cmd)
     198              : {
     199           30 :   struct IteratorContext *ic = cls;
     200           30 :   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           30 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     209              :               "Checking if command %s is relevant for debit history\n",
     210              :               cmd->label);
     211           30 :   if ( (GNUNET_OK !=
     212           30 :         TALER_TESTING_get_trait_bank_row (cmd,
     213            8 :                                           &row_id)) ||
     214              :        (GNUNET_OK !=
     215            8 :         TALER_TESTING_get_trait_debit_payto_uri (cmd,
     216            8 :                                                  &debit_account)) ||
     217              :        (GNUNET_OK !=
     218            8 :         TALER_TESTING_get_trait_credit_payto_uri (cmd,
     219            8 :                                                   &credit_account)) ||
     220              :        (GNUNET_OK !=
     221            8 :         TALER_TESTING_get_trait_amount (cmd,
     222            8 :                                         &amount)) ||
     223              :        (GNUNET_OK !=
     224            8 :         TALER_TESTING_get_trait_wtid (cmd,
     225            2 :                                       &wtid)) ||
     226              :        (GNUNET_OK !=
     227            2 :         TALER_TESTING_get_trait_exchange_url (cmd,
     228              :                                               &exchange_base_url)) )
     229           28 :     return;   /* not an event we care about */
     230              :   /* Seek "/history/outgoing" starting row.  */
     231            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     232              :               "Command %s is relevant for debit history!\n",
     233              :               cmd->label);
     234            2 :   if ( (NULL != ic->row_id_start) &&
     235            0 :        (*(ic->row_id_start) == *row_id) &&
     236            0 :        (! ic->ok) )
     237              :   {
     238              :     /* Until here, nothing counted. */
     239            0 :     ic->ok = true;
     240            0 :     return;
     241              :   }
     242              :   /* when 'start' was _not_ given, then ok == GNUNET_YES */
     243            2 :   if (! ic->ok)
     244            0 :     return;   /* skip until we find the marker */
     245            2 :   if (ic->pos >= GNUNET_MAX (hs->num_results,
     246              :                              -hs->num_results) )
     247              :   {
     248            0 :     TALER_LOG_DEBUG ("Hit history limit\n");
     249            0 :     return;
     250              :   }
     251            2 :   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            2 :   if (ic->pos == ic->total)
     257            0 :     GNUNET_array_grow (ic->h,
     258              :                        ic->total,
     259              :                        ic->pos * 2);
     260            2 :   ic->h[ic->pos].c_url = GNUNET_strdup (credit_account->full_payto);
     261            2 :   ic->h[ic->pos].details.credit_account_uri.full_payto
     262            2 :     = ic->h[ic->pos].c_url;
     263            2 :   ic->h[ic->pos].details.amount = *amount;
     264            2 :   ic->h[ic->pos].row_id = *row_id;
     265            2 :   ic->h[ic->pos].details.wtid = *wtid;
     266            2 :   ic->h[ic->pos].details.exchange_base_url = exchange_base_url;
     267            2 :   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            4 : build_history (struct HistoryState *hs,
     284              :                struct History **rh)
     285              : {
     286            4 :   struct TALER_TESTING_Interpreter *is = hs->is;
     287            4 :   struct IteratorContext ic = {
     288              :     .hs = hs
     289              :   };
     290              : 
     291            4 :   if (NULL != hs->start_row_reference)
     292              :   {
     293              :     const struct TALER_TESTING_Command *add_incoming_cmd;
     294              : 
     295            0 :     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            0 :     add_incoming_cmd = TALER_TESTING_interpreter_lookup_command (
     300              :       is,
     301              :       hs->start_row_reference);
     302            0 :     GNUNET_assert (NULL != add_incoming_cmd);
     303            0 :     GNUNET_assert (GNUNET_OK ==
     304              :                    TALER_TESTING_get_trait_row (add_incoming_cmd,
     305              :                                                 &ic.row_id_start));
     306              :   }
     307              : 
     308            4 :   ic.ok = false;
     309            4 :   if (NULL == ic.row_id_start)
     310            4 :     ic.ok = true;
     311            4 :   GNUNET_array_grow (ic.h,
     312              :                      ic.total,
     313              :                      4);
     314            4 :   GNUNET_assert (0 != hs->num_results);
     315            4 :   TALER_TESTING_iterate (is,
     316            4 :                          hs->num_results > 0,
     317              :                          &command_cb,
     318              :                          &ic);
     319            4 :   GNUNET_assert (ic.ok);
     320            4 :   GNUNET_array_grow (ic.h,
     321              :                      ic.total,
     322              :                      ic.pos);
     323            4 :   if (0 == ic.pos)
     324            2 :     TALER_LOG_DEBUG ("Empty credit history computed\n");
     325            4 :   *rh = ic.h;
     326            4 :   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            2 : check_result (struct History *h,
     343              :               uint64_t total,
     344              :               unsigned int off,
     345              :               const struct TALER_BANK_DebitDetails *details)
     346              : {
     347            2 :   if (off >= total)
     348              :   {
     349            0 :     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            0 :     print_expected (h,
     355              :                     total,
     356              :                     off);
     357            0 :     return GNUNET_SYSERR;
     358              :   }
     359            2 :   if ( (0 != GNUNET_memcmp (&h[off].details.wtid,
     360            2 :                             &details->wtid)) ||
     361            2 :        (0 != TALER_amount_cmp (&h[off].details.amount,
     362            2 :                                &details->amount)) ||
     363            2 :        (0 != TALER_full_payto_normalize_and_cmp (
     364            2 :           h[off].details.credit_account_uri,
     365              :           details->credit_account_uri)) )
     366              :   {
     367            0 :     GNUNET_break (0);
     368            0 :     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            0 :     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            0 :     print_expected (h,
     379              :                     total,
     380              :                     off);
     381            0 :     return GNUNET_SYSERR;
     382              :   }
     383            2 :   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            4 : history_cb (void *cls,
     399              :             const struct TALER_BANK_DebitHistoryResponse *dhr)
     400              : {
     401            4 :   struct HistoryState *hs = cls;
     402            4 :   struct TALER_TESTING_Interpreter *is = hs->is;
     403              : 
     404            4 :   hs->hh = NULL;
     405            4 :   switch (dhr->http_status)
     406              :   {
     407            0 :   case 0:
     408            0 :     GNUNET_break (0);
     409            0 :     goto error;
     410            2 :   case MHD_HTTP_OK:
     411            4 :     for (unsigned int i = 0; i<dhr->details.ok.details_length; i++)
     412              :     {
     413            2 :       const struct TALER_BANK_DebitDetails *dd =
     414            2 :         &dhr->details.ok.details[i];
     415              : 
     416              :       /* check current element */
     417            2 :       if (GNUNET_OK !=
     418            2 :           check_result (hs->h,
     419            2 :                         hs->total,
     420            2 :                         hs->results_obtained,
     421              :                         dd))
     422              :       {
     423            0 :         GNUNET_break (0);
     424            0 :         json_dumpf (dhr->response,
     425              :                     stderr,
     426              :                     JSON_COMPACT);
     427            0 :         hs->failed = true;
     428            0 :         hs->hh = NULL;
     429            0 :         TALER_TESTING_interpreter_fail (is);
     430            0 :         return;
     431              :       }
     432            2 :       hs->results_obtained++;
     433              :     }
     434            2 :     if (hs->results_obtained != (uint64_t) hs->total)
     435              :     {
     436              :       /* bank returned fewer results than we expected */
     437            0 :       GNUNET_break (0);
     438            0 :       hs->failed = true;
     439            0 :       goto error;
     440              :     }
     441            2 :     TALER_TESTING_interpreter_next (is);
     442            2 :     return;
     443            2 :   case MHD_HTTP_NO_CONTENT:
     444            2 :     if (0 == hs->total)
     445              :     {
     446              :       /* not found is OK for empty history */
     447            2 :       TALER_TESTING_interpreter_next (is);
     448            2 :       return;
     449              :     }
     450            0 :     GNUNET_break (0);
     451            0 :     goto error;
     452            0 :   case MHD_HTTP_NOT_FOUND:
     453            0 :     if (0 == hs->total)
     454              :     {
     455              :       /* not found is OK for empty history */
     456            0 :       TALER_TESTING_interpreter_next (is);
     457            0 :       return;
     458              :     }
     459            0 :     GNUNET_break (0);
     460            0 :     goto error;
     461            0 :   default:
     462            0 :     hs->hh = NULL;
     463            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     464              :                 "Unwanted response code from /history/incoming: %u\n",
     465              :                 dhr->http_status);
     466            0 :     TALER_TESTING_interpreter_fail (is);
     467            0 :     return;
     468              :   }
     469            0 : error:
     470            0 :   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            0 :   print_expected (hs->h,
     479              :                   hs->total,
     480              :                   UINT_MAX);
     481            0 :   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            4 : history_run (void *cls,
     494              :              const struct TALER_TESTING_Command *cmd,
     495              :              struct TALER_TESTING_Interpreter *is)
     496              : {
     497            4 :   struct HistoryState *hs = cls;
     498            4 :   uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX;
     499              :   const uint64_t *row_ptr;
     500              : 
     501              :   (void) cmd;
     502            4 :   hs->is = is;
     503              :   /* Get row_id from trait. */
     504            4 :   if (NULL != hs->start_row_reference)
     505              :   {
     506              :     const struct TALER_TESTING_Command *history_cmd;
     507              : 
     508              :     history_cmd
     509            0 :       = TALER_TESTING_interpreter_lookup_command (is,
     510              :                                                   hs->start_row_reference);
     511              : 
     512            0 :     if (NULL == history_cmd)
     513            0 :       TALER_TESTING_FAIL (is);
     514            0 :     if (GNUNET_OK !=
     515            0 :         TALER_TESTING_get_trait_row (history_cmd,
     516              :                                      &row_ptr))
     517            0 :       TALER_TESTING_FAIL (is);
     518              :     else
     519            0 :       row_id = *row_ptr;
     520            0 :     TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
     521              :                      (unsigned long long) row_id);
     522              :   }
     523            4 :   hs->total = build_history (hs,
     524              :                              &hs->h);
     525            4 :   hs->hh = TALER_BANK_debit_history (
     526              :     TALER_TESTING_interpreter_get_context (is),
     527            4 :     &hs->auth,
     528              :     row_id,
     529            4 :     hs->num_results,
     530            4 :     GNUNET_TIME_UNIT_ZERO,
     531              :     &history_cb,
     532              :     hs);
     533            4 :   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            4 : history_cleanup (void *cls,
     546              :                  const struct TALER_TESTING_Command *cmd)
     547              : {
     548            4 :   struct HistoryState *hs = cls;
     549              : 
     550              :   (void) cmd;
     551            4 :   if (NULL != hs->hh)
     552              :   {
     553            0 :     TALER_TESTING_command_incomplete (hs->is,
     554              :                                       cmd->label);
     555            0 :     TALER_BANK_debit_history_cancel (hs->hh);
     556              :   }
     557            6 :   for (unsigned int off = 0; off<hs->total; off++)
     558              :   {
     559            2 :     GNUNET_free (hs->h[off].c_url);
     560              :   }
     561            4 :   GNUNET_free (hs->h);
     562            4 :   GNUNET_free (hs);
     563            4 : }
     564              : 
     565              : 
     566              : struct TALER_TESTING_Command
     567            4 : 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            4 :   hs = GNUNET_new (struct HistoryState);
     575            4 :   hs->account_url = auth->wire_gateway_url;
     576            4 :   hs->start_row_reference = start_row_reference;
     577            4 :   hs->num_results = num_results;
     578            4 :   hs->auth = *auth;
     579              : 
     580              :   {
     581            4 :     struct TALER_TESTING_Command cmd = {
     582              :       .label = label,
     583              :       .cls = hs,
     584              :       .run = &history_run,
     585              :       .cleanup = &history_cleanup
     586              :     };
     587              : 
     588            4 :     return cmd;
     589              :   }
     590              : }
     591              : 
     592              : 
     593              : /* end of testing_api_cmd_bank_history_debit.c */
        

Generated by: LCOV version 2.0-1