LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_bank_history_debit.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 107 167 64.1 %
Date: 2022-08-25 06:15:09 Functions: 6 7 85.7 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14