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

Generated by: LCOV version 1.14