LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_bank_history_credit.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 103 159 64.8 %
Date: 2021-08-30 06:43:37 Functions: 7 8 87.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2018-2020 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_credit.c
      21             :  * @brief command to check the /history/incoming 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             : /**
      35             :  * Item in the transaction history, as reconstructed from the
      36             :  * command history.
      37             :  */
      38             : struct History
      39             : {
      40             : 
      41             :   /**
      42             :    * Wire details.
      43             :    */
      44             :   struct TALER_BANK_CreditDetails details;
      45             : 
      46             :   /**
      47             :    * Serial ID of the wire transfer.
      48             :    */
      49             :   uint64_t row_id;
      50             : 
      51             :   /**
      52             :    * URL to free.
      53             :    */
      54             :   char *url;
      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             :   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             :    * Handle to a pending "history" operation.
      82             :    */
      83             :   struct TALER_BANK_CreditHistoryHandle *hh;
      84             : 
      85             :   /**
      86             :    * Authentication data for the operation.
      87             :    */
      88             :   struct TALER_BANK_AuthenticationData auth;
      89             : 
      90             :   /**
      91             :    * Expected number of results (= rows).
      92             :    */
      93             :   uint64_t results_obtained;
      94             : 
      95             :   /**
      96             :    * Set to GNUNET_YES if the callback detects something
      97             :    * unexpected.
      98             :    */
      99             :   int failed;
     100             : 
     101             :   /**
     102             :    * Expected history.
     103             :    */
     104             :   struct History *h;
     105             : 
     106             :   /**
     107             :    * Length of @e h
     108             :    */
     109             :   unsigned int total;
     110             : 
     111             : };
     112             : 
     113             : 
     114             : /**
     115             :  * Offer internal data to other commands.
     116             :  *
     117             :  * @param cls closure.
     118             :  * @param[out] ret set to the wanted data.
     119             :  * @param trait name of the trait.
     120             :  * @param index index number of the traits to be returned.
     121             :  *
     122             :  * @return #GNUNET_OK on success
     123             :  */
     124             : static int
     125          10 : history_traits (void *cls,
     126             :                 const void **ret,
     127             :                 const char *trait,
     128             :                 unsigned int index)
     129             : {
     130             :   (void) cls;
     131             :   (void) ret;
     132             :   (void) trait;
     133             :   (void) index;
     134             :   /* Must define this function because some callbacks
     135             :    * look for certain traits on _all_ the commands. */
     136          10 :   return GNUNET_SYSERR;
     137             : }
     138             : 
     139             : 
     140             : /**
     141             :  * Log which history we expected.  Called when an error occurs.
     142             :  *
     143             :  * @param h what we expected.
     144             :  * @param h_len number of entries in @a h.
     145             :  * @param off position of the mismatch.
     146             :  */
     147             : static void
     148           0 : print_expected (struct History *h,
     149             :                 unsigned int h_len,
     150             :                 unsigned int off)
     151             : {
     152           0 :   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     153             :               "Transaction history (credit) mismatch at position %u/%u\n",
     154             :               off,
     155             :               h_len);
     156           0 :   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     157             :               "Expected history:\n");
     158           0 :   for (unsigned int i = 0; i<h_len; i++)
     159             :   {
     160           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     161             :                 "H(%u): %s (serial: %llu, subject: %s,"
     162             :                 " counterpart: %s)\n",
     163             :                 i,
     164             :                 TALER_amount2s (&h[i].details.amount),
     165             :                 (unsigned long long) h[i].row_id,
     166             :                 TALER_B2S (&h[i].details.reserve_pub),
     167             :                 h[i].details.debit_account_uri);
     168             :   }
     169           0 : }
     170             : 
     171             : 
     172             : /**
     173             :  * This function constructs the list of history elements that
     174             :  * interest the account number of the caller.  It has two main
     175             :  * loops: the first to figure out how many history elements have
     176             :  * to be allocated, and the second to actually populate every
     177             :  * element.
     178             :  *
     179             :  * @param is interpreter state (supposedly having the
     180             :  *        current CMD pointing at a "history" CMD).
     181             :  * @param[out] rh history array to initialize.
     182             :  * @return number of entries in @a rh.
     183             :  */
     184             : static unsigned int
     185           4 : build_history (struct TALER_TESTING_Interpreter *is,
     186             :                struct History **rh)
     187             : {
     188           4 :   struct HistoryState *hs = is->commands[is->ip].cls;
     189             :   unsigned int total;
     190             :   unsigned int pos;
     191             :   struct History *h;
     192             :   const struct TALER_TESTING_Command *add_incoming_cmd;
     193             :   int inc;
     194             :   unsigned int start;
     195             :   unsigned int end;
     196             : 
     197             :   /* @var turns GNUNET_YES whenever either no 'start' value was
     198             :    *      given for the history query, or the given value is found
     199             :    *      in the list of all the CMDs. *///
     200             :   int ok;
     201           4 :   const uint64_t *row_id_start = NULL;
     202             : 
     203           4 :   if (NULL != hs->start_row_reference)
     204             :   {
     205           0 :     TALER_LOG_INFO ("`%s': start row given via reference `%s'\n",
     206             :                     TALER_TESTING_interpreter_get_current_label (is),
     207             :                     hs->start_row_reference);
     208             :     add_incoming_cmd
     209           0 :       = TALER_TESTING_interpreter_lookup_command (is,
     210             :                                                   hs->start_row_reference);
     211           0 :     GNUNET_assert (NULL != add_incoming_cmd);
     212           0 :     GNUNET_assert (GNUNET_OK ==
     213             :                    TALER_TESTING_get_trait_uint64 (add_incoming_cmd,
     214             :                                                    0,
     215             :                                                    &row_id_start));
     216             :   }
     217             : 
     218           4 :   GNUNET_assert (0 != hs->num_results);
     219           4 :   if (0 == is->ip)
     220             :   {
     221           2 :     TALER_LOG_DEBUG ("Checking history at FIRST transaction (EMPTY)\n");
     222           2 :     *rh = NULL;
     223           2 :     return 0;
     224             :   }
     225             : 
     226           2 :   if (hs->num_results > 0)
     227             :   {
     228           2 :     inc = 1;  /* _inc_rement */
     229           2 :     start = 0;
     230           2 :     end = is->ip - 1;
     231             :   }
     232             :   else
     233             :   {
     234           0 :     inc = -1;
     235           0 :     start = is->ip - 1;
     236           0 :     end = 0;
     237             :   }
     238             : 
     239           2 :   ok = GNUNET_NO;
     240           2 :   if (NULL == row_id_start)
     241           2 :     ok = GNUNET_YES;
     242           2 :   h = NULL;
     243           2 :   total = 0;
     244           2 :   GNUNET_array_grow (h,
     245             :                      total,
     246             :                      4);
     247           2 :   pos = 0;
     248          10 :   for (unsigned int off = start; off != end + inc; off += inc)
     249             :   {
     250           8 :     const struct TALER_TESTING_Command *cmd = &is->commands[off];
     251             :     const uint64_t *row_id;
     252             :     const char *credit_account;
     253             :     const char *debit_account;
     254             :     const struct TALER_Amount *amount;
     255             :     const struct TALER_ReservePublicKeyP *reserve_pub;
     256             :     const char *exchange_credit_url;
     257             : 
     258             :     /* The following command allows us to skip over those CMDs
     259             :      * that do not offer a "row_id" trait.  Such skipped CMDs are
     260             :      * not interesting for building a history. *///
     261           8 :     if ( (GNUNET_OK !=
     262           8 :           TALER_TESTING_get_trait_bank_row (cmd,
     263           2 :                                             &row_id)) ||
     264             :          (GNUNET_OK !=
     265           2 :           TALER_TESTING_get_trait_payto (cmd,
     266             :                                          TALER_TESTING_PT_CREDIT,
     267           2 :                                          &credit_account)) ||
     268             :          (GNUNET_OK !=
     269           2 :           TALER_TESTING_get_trait_payto (cmd,
     270             :                                          TALER_TESTING_PT_DEBIT,
     271           2 :                                          &debit_account)) ||
     272             :          (GNUNET_OK !=
     273           2 :           TALER_TESTING_get_trait_amount_obj (cmd,
     274             :                                               0,
     275           2 :                                               &amount)) ||
     276             :          (GNUNET_OK !=
     277           2 :           TALER_TESTING_get_trait_reserve_pub (cmd,
     278             :                                                0,
     279           2 :                                                &reserve_pub)) ||
     280             :          (GNUNET_OK !=
     281           2 :           TALER_TESTING_get_trait_url (cmd,
     282             :                                        TALER_TESTING_UT_EXCHANGE_BANK_ACCOUNT_URL,
     283             :                                        &exchange_credit_url)) )
     284           6 :       continue; /* not an interesting event */
     285             :     /* Seek "/history/incoming" starting row.  */
     286           2 :     if ( (NULL != row_id_start) &&
     287           0 :          (*row_id_start == *row_id) &&
     288             :          (GNUNET_NO == ok) )
     289             :     {
     290             :       /* Until here, nothing counted. */
     291           0 :       ok = GNUNET_YES;
     292           0 :       continue;
     293             :     }
     294             :     /* when 'start' was _not_ given, then ok == GNUNET_YES */
     295           2 :     if (GNUNET_NO == ok)
     296           0 :       continue; /* skip until we find the marker */
     297           2 :     if (0 != strcasecmp (hs->account_url,
     298             :                          exchange_credit_url))
     299           0 :       continue; /* account mismatch */
     300           2 :     if (total >= GNUNET_MAX (hs->num_results,
     301             :                              -hs->num_results) )
     302             :     {
     303           0 :       TALER_LOG_DEBUG ("Hit history limit\n");
     304           0 :       break;
     305             :     }
     306           2 :     TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
     307             :                     debit_account,
     308             :                     credit_account,
     309             :                     hs->account_url);
     310             :     /* found matching record, make sure we have room */
     311           2 :     if (pos == total)
     312           0 :       GNUNET_array_grow (h,
     313             :                          total,
     314             :                          pos * 2);
     315           2 :     h[pos].url = GNUNET_strdup (debit_account);
     316           2 :     h[pos].details.debit_account_uri = h[pos].url;
     317           2 :     h[pos].details.amount = *amount;
     318           2 :     h[pos].row_id = *row_id;
     319           2 :     h[pos].details.reserve_pub = *reserve_pub;
     320           2 :     h[pos].details.credit_account_uri = exchange_credit_url;
     321           2 :     pos++;
     322             :   }
     323           2 :   GNUNET_assert (GNUNET_YES == ok);
     324           2 :   GNUNET_array_grow (h,
     325             :                      total,
     326             :                      pos);
     327           2 :   if (0 == pos)
     328           0 :     TALER_LOG_DEBUG ("Empty credit history computed\n");
     329           2 :   *rh = h;
     330           2 :   return total;
     331             : }
     332             : 
     333             : 
     334             : /**
     335             :  * Check that the "/history/incoming" response matches the
     336             :  * CMD whose offset in the list of CMDs is @a off.
     337             :  *
     338             :  * @param h expected history (array)
     339             :  * @param total length of @a h
     340             :  * @param off the offset (of the CMD list) where the command
     341             :  *        to check is.
     342             :  * @param details the expected transaction details.
     343             :  * @return #GNUNET_OK if the transaction is what we expect.
     344             :  */
     345             : static int
     346           2 : check_result (struct History *h,
     347             :               unsigned int total,
     348             :               unsigned int off,
     349             :               const struct TALER_BANK_CreditDetails *details)
     350             : {
     351           2 :   if (off >= total)
     352             :   {
     353           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     354             :                 "Test says history has at most %u"
     355             :                 " results, but got result #%u to check\n",
     356             :                 total,
     357             :                 off);
     358           0 :     print_expected (h,
     359             :                     total,
     360             :                     off);
     361           0 :     return GNUNET_SYSERR;
     362             :   }
     363           2 :   if ( (0 != GNUNET_memcmp (&h[off].details.reserve_pub,
     364           2 :                             &details->reserve_pub)) ||
     365           2 :        (0 != TALER_amount_cmp (&h[off].details.amount,
     366           2 :                                &details->amount)) ||
     367           2 :        (0 != strcasecmp (h[off].details.debit_account_uri,
     368             :                          details->debit_account_uri)) )
     369             :   {
     370           0 :     GNUNET_break (0);
     371           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     372             :                 "expected debit_account_uri: %s\n",
     373             :                 details->debit_account_uri);
     374           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     375             :                 "actual debit_account_uri: %s\n",
     376             :                 h[off].details.debit_account_uri);
     377           0 :     print_expected (h,
     378             :                     total,
     379             :                     off);
     380           0 :     return GNUNET_SYSERR;
     381             :   }
     382           2 :   return GNUNET_OK;
     383             : }
     384             : 
     385             : 
     386             : /**
     387             :  * This callback will (1) check that the HTTP response code
     388             :  * is acceptable and (2) that the history is consistent.  The
     389             :  * consistency is checked by going through all the past CMDs,
     390             :  * reconstructing then the expected history as of those, and
     391             :  * finally check it against what the bank returned.
     392             :  *
     393             :  * @param cls closure.
     394             :  * @param http_status HTTP response code, #MHD_HTTP_OK (200)
     395             :  *        for successful status request 0 if the bank's reply is
     396             :  *        bogus (fails to follow the protocol),
     397             :  *        #MHD_HTTP_NO_CONTENT if there are no more results; on
     398             :  *        success the last callback is always of this status
     399             :  *        (even if `abs(num_results)` were already returned).
     400             :  * @param ec taler status code.
     401             :  * @param row_id monotonically increasing counter corresponding to
     402             :  *        the transaction.
     403             :  * @param details details about the wire transfer.
     404             :  * @param json detailed response from the HTTPD, or NULL if
     405             :  *        reply was not in JSON.
     406             :  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
     407             :  */
     408             : static int
     409           6 : history_cb (void *cls,
     410             :             unsigned int http_status,
     411             :             enum TALER_ErrorCode ec,
     412             :             uint64_t row_id,
     413             :             const struct TALER_BANK_CreditDetails *details,
     414             :             const json_t *json)
     415             : {
     416           6 :   struct TALER_TESTING_Interpreter *is = cls;
     417           6 :   struct HistoryState *hs = is->commands[is->ip].cls;
     418             : 
     419             :   (void) row_id;
     420           6 :   if (NULL == details)
     421             :   {
     422           4 :     hs->hh = NULL;
     423           4 :     if ( (hs->results_obtained != hs->total) ||
     424           4 :          (GNUNET_YES == hs->failed) ||
     425             :          (MHD_HTTP_NO_CONTENT != http_status) )
     426             :     {
     427           0 :       GNUNET_break (0);
     428           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     429             :                   "Expected history of length %u, got %llu;"
     430             :                   " HTTP status code: %u/%d, failed: %d\n",
     431             :                   hs->total,
     432             :                   (unsigned long long) hs->results_obtained,
     433             :                   http_status,
     434             :                   (int) ec,
     435             :                   hs->failed);
     436           0 :       print_expected (hs->h,
     437             :                       hs->total,
     438             :                       UINT_MAX);
     439           0 :       TALER_TESTING_interpreter_fail (is);
     440           0 :       return GNUNET_SYSERR;
     441             :     }
     442           4 :     TALER_TESTING_interpreter_next (is);
     443           4 :     return GNUNET_OK;
     444             :   }
     445           2 :   if (MHD_HTTP_OK != http_status)
     446             :   {
     447           0 :     hs->hh = NULL;
     448           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     449             :                 "Unwanted response code from /history/incoming: %u\n",
     450             :                 http_status);
     451           0 :     TALER_TESTING_interpreter_fail (is);
     452           0 :     return GNUNET_SYSERR;
     453             :   }
     454             : 
     455             :   /* check current element */
     456           2 :   if (GNUNET_OK != check_result (hs->h,
     457             :                                  hs->total,
     458           2 :                                  hs->results_obtained,
     459             :                                  details))
     460             :   {
     461             :     char *acc;
     462             : 
     463           0 :     GNUNET_break (0);
     464           0 :     acc = json_dumps (json,
     465             :                       JSON_COMPACT);
     466           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     467             :                 "Result %u was `%s'\n",
     468             :                 (unsigned int) hs->results_obtained++,
     469             :                 acc);
     470           0 :     if (NULL != acc)
     471           0 :       free (acc);
     472           0 :     hs->failed = GNUNET_YES;
     473           0 :     return GNUNET_SYSERR;
     474             :   }
     475           2 :   hs->results_obtained++;
     476           2 :   return GNUNET_OK;
     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             :   /* Get row_id from trait. */
     498           4 :   if (NULL != hs->start_row_reference)
     499             :   {
     500             :     const struct TALER_TESTING_Command *history_cmd;
     501             : 
     502           0 :     history_cmd = TALER_TESTING_interpreter_lookup_command
     503             :                     (is, hs->start_row_reference);
     504             : 
     505           0 :     if (NULL == history_cmd)
     506           0 :       TALER_TESTING_FAIL (is);
     507             : 
     508           0 :     if (GNUNET_OK !=
     509           0 :         TALER_TESTING_get_trait_uint64 (history_cmd,
     510             :                                         0,
     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 (is,
     519             :                              &hs->h);
     520           4 :   hs->hh = TALER_BANK_credit_history (is->ctx,
     521           4 :                                       &hs->auth,
     522             :                                       row_id,
     523           4 :                                       hs->num_results,
     524             :                                       GNUNET_TIME_UNIT_ZERO,
     525             :                                       &history_cb,
     526             :                                       is);
     527           4 :   GNUNET_assert (NULL != hs->hh);
     528             : }
     529             : 
     530             : 
     531             : /**
     532             :  * Free the state from a "history" CMD, and possibly cancel
     533             :  * a pending operation thereof.
     534             :  *
     535             :  * @param cls closure.
     536             :  * @param cmd the command which is being cleaned up.
     537             :  */
     538             : static void
     539           4 : history_cleanup (void *cls,
     540             :                  const struct TALER_TESTING_Command *cmd)
     541             : {
     542           4 :   struct HistoryState *hs = cls;
     543             : 
     544             :   (void) cmd;
     545           4 :   if (NULL != hs->hh)
     546             :   {
     547           0 :     TALER_LOG_WARNING ("/history/incoming did not complete\n");
     548           0 :     TALER_BANK_credit_history_cancel (hs->hh);
     549             :   }
     550           4 :   GNUNET_free (hs->account_url);
     551           6 :   for (unsigned int off = 0; off<hs->total; off++)
     552           2 :     GNUNET_free (hs->h[off].url);
     553           4 :   GNUNET_free (hs->h);
     554           4 :   GNUNET_free (hs);
     555           4 : }
     556             : 
     557             : 
     558             : struct TALER_TESTING_Command
     559           4 : TALER_TESTING_cmd_bank_credits (
     560             :   const char *label,
     561             :   const struct TALER_BANK_AuthenticationData *auth,
     562             :   const char *start_row_reference,
     563             :   long long num_results)
     564             : {
     565             :   struct HistoryState *hs;
     566             : 
     567           4 :   hs = GNUNET_new (struct HistoryState);
     568           4 :   hs->account_url = GNUNET_strdup (auth->wire_gateway_url);
     569           4 :   hs->start_row_reference = start_row_reference;
     570           4 :   hs->num_results = num_results;
     571           4 :   hs->auth = *auth;
     572             :   {
     573           4 :     struct TALER_TESTING_Command cmd = {
     574             :       .label = label,
     575             :       .cls = hs,
     576             :       .run = &history_run,
     577             :       .cleanup = &history_cleanup,
     578             :       .traits = &history_traits
     579             :     };
     580             : 
     581           4 :     return cmd;
     582             :   }
     583             : }
     584             : 
     585             : 
     586             : /* end of testing_api_cmd_credit_history.c */

Generated by: LCOV version 1.14