LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_bank_history_debit.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 104 166 62.7 %
Date: 2025-06-05 21:03:14 Functions: 7 8 87.5 %

          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 "platform.h"
      25             : #include "taler_json_lib.h"
      26             : #include <gnunet/gnunet_curl_lib.h>
      27             : #include "taler_exchange_service.h"
      28             : #include "taler_testing_lib.h"
      29             : #include "taler_fakebank_lib.h"
      30             : #include "taler_bank_service.h"
      31             : #include "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 1.16