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

Generated by: LCOV version 2.0-1